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