Node on ExecJS is sloooooow
I've been talking with a friend of mine about ExecJS and when he told me he uses therubyracer my knee-jerk response was: "Why, just use node.js". Then, as a conscious defense against cached thoughts I've decided to test this hypothesis.
The script I've used was extremely simple:
require 'benchmark' require 'execjs' require 'therubyracer' js_code = '2+2' Benchmark.bm(12) do |benchmark| ExecJS.runtime = ExecJS::Runtimes::Node benchmark.report('node.js') do 100.times { ExecJS.eval(js_code) } end ExecJS.runtime = ExecJS::Runtimes::RubyRacer benchmark.report('therubyracer') do 100.times { ExecJS.eval(js_code) } end end
The results have shaken my long-held belief that Node.js is the preferred runtime for ExecJS, a belief that I've held for almost as long as I'm a Rails developer. It turns out node.js when used with ExecJS is simply slow as a snail:
user system total real node.js 0.090000 0.060000 16.320000 ( 17.016673) therubyracer 0.070000 0.020000 0.090000 ( 0.089758)
We've rechecked the results on Maciek's computer and my spare server and they look the same. I had a hunch that maybe it's the cost of spawning a separate process multiple times to do a tiny calculation but even testing with a huge uncompressed math.js file showed similarly huge difference: 1
user system total real node.js 1.030000 0.340000 35.950000 ( 36.717392) therubyracer 1.990000 0.400000 2.390000 ( 2.445502)
I don't think I want to know how many precious minutes of my life were wasted because of the slower asset compilation due to that.
The lesson I've learned once again from this is that you should recheck your knowledge from time to time - otherwise you'll never realize you're wrong and you'll keep repeating your mistake over and over again until somebody points that out or you embarass yourself in front of the audience.
Happy hacking!
UPDATE: Paweł Świątkowski in his comment has pointed out that using Benchmark.bm is risky because it doesn't take GC and memory allocation into account and much better idea would be to use Benchmark.bmbm. Unfortunately I've discovered that bmbm is NOT a drop-in replacement, mainly due to the fact they are differently implemented and have a major difference in the way they work: Benchmark.bm runs all the code passed to it's block while Benchmark.bmbm only does so with report blocks. This has caused Paweł's original test to run twice with therubyracer, making it look like there's no speed difference between those two. Taking that difference into account proves once again that is not the case.
If you want to repeat the test with math.js remember to remove last two semicolons in the file - for some reason execjs hates them ↩









