Isomorfeus Speednode
A fast runtime for ExecJS using node js. Works on Linux, BSDs, MacOS and Windows. Inspired by execjs-fastnode.
Community and Support
At the Isomorfeus Framework Project
Installation
In Gemfile:
gem 'isomorfeus-speednode'
, then bundle install
Configuration
Isomorfeus-speednode provides one node based runtime Speednode
which runs scripts in node vms.
The runtime can be chosen by:
ExecJS.runtime = ExecJS::Runtimes::Speednode
If node cant find node modules for the permissive contexts (see below), its possible to set the load path before assigning the runtime:
ENV['NODE_PATH'] = './node_modules'
Contexts
Each ExecJS context runs in a node vm. Speednode offers two kinds of contexts:
- a compatible context, which is compatible with default ExecJS behavior.
- a permissive context, which is more permissive and allows to
require
node modules.
Compatible
A compatible context can be created with the standard ExecJS.compile
or code can be executed within a compatible context by using the standard ExecJS.eval
or ExecJS.exec
.
Example for a compatible context:
compat_context = ExecJS.compile('Test = "test"')
compat_context.eval('1+1')
Permissive
A permissive context can be created with ExecJS.permissive_compile
or code can be executed within a permissive context by using
ExecJS.permissive_eval
or ExecJS.permissive_exec
.
Example for a permissive context:
perm_context = ExecJS.permissive_compile('Test = "test"')
perm_context.eval('1+1')
Evaluation in a permissive context:
ExecJS.permissive_eval('1+1')
Stopping Contexts
Contexts can be stopped programmatically. If all contexts of a VM are stopped, the VM itself will be shut down with Node exiting, freeing memory and resources.
context = ExecJS.compile('Test = "test"') # will start a node process
ExecJS::Runtimes::Speednode.stop_context(context) # will kill the node process
Precompiling and Storing scripts for repeated execution
Scripts can be precompiled and stored for repeated execution, which leads to a significant performance improvement, especially for larger scripts:
context = ExecJS.compile('Test = "test"')
context.add_script(key: 'super', source: some_large_javascript) # will compile and store the script
context.eval_script(key: 'super') # will run the precompiled script in the context
For the actual performance benefit see below.
Async function support
Its possible to call async functions synchronously from ruby using Context#await:
context = ExecJS.compile('')
context.eval <<~JAVASCRIPT
async function foo(val) {
return new Promise(function (resolve, reject) { resolve(val); });
}
JAVASCRIPT
context.await("foo('test')") # => 'test'
Attaching ruby methods to Permissive Contexts
Ruby methods can be attached to Permissive Contexts using Context#attach:
context = ExecJS.permissive_compile(SOURCE)
context.attach('foo') { |v| v }
context.await('foo("bar")')
The attached method is reflected in the context by a async javascript function. From within javascript the ruby method is best called using await:
r = await foo('test');
or via context#await as in above example. Attaching and calling ruby methods to/from permissive contexts is not that fast. It is recommended to use it sparingly.
Benchmarks
Highly scientific, maybe.
1000 rounds using isomorfeus-speednode 0.6.1 with node 18.15.0 on a older CPU on Linux (ctx = using context, scsc = using precompiled scripts):
standard ExecJS CoffeeScript eval benchmark:
user system total real
Isomorfeus Speednode Node.js (V8): 0.107551 0.024227 0.131778 ( 1.034893)
Isomorfeus Speednode Node.js (V8) ctx: 0.081142 0.064432 0.145574 ( 0.878249)
Isomorfeus Speednode Node.js (V8) scsc: 0.058941 0.040266 0.099207 ( 0.525479)
mini_racer (0.6.3, libv8-node 16.10.0): 0.563453 0.416156 0.979609 ( 0.628226)
mini_racer (0.6.3, libv8-node 16.10.0) ctx: 0.393274 0.187259 0.580533 ( 0.434056)
eval overhead benchmark:
user system total real
Isomorfeus Speednode Node.js (V8): 0.065491 0.026181 0.091672 ( 0.196204)
Isomorfeus Speednode Node.js (V8) ctx: 0.060876 0.029535 0.090411 ( 0.197763)
Isomorfeus Speednode Node.js (V8) scsc: 0.036967 0.045451 0.082418 ( 0.133337)
mini_racer (0.6.3, libv8-node 16.10.0): 0.070333 0.056378 0.126711 ( 0.100380)
mini_racer (0.6.3, libv8-node 16.10.0) ctx: 0.072719 0.049049 0.121768 ( 0.095643)
To run benchmarks:
- clone repo
bundle install
bundle exec rake bench
Tests
To run tests:
- clone repo
bundle install
bundle exec rake