TruffleRuby

TruffleRuby is an implementation of the Ruby programming language that aims to:

  • Run idiomatic Ruby code faster
  • Run Ruby code in parallel
  • Boot Ruby applications in less time
  • Execute C extensions in a managed environment
  • Provide new tooling such as debuggers and monitoring
  • Add fast and low-overhead interoperability with languages like JavaScript, Python 3 and R
  • All while maintaining very high compatibility with the standard implementation of Ruby

If you’re attempting to experiment with deploying TruffleRuby to production we’d encourage you to contact us so we can help you understand what is possible at the moment and to help solve any issues for you.

Installing TruffleRuby

TruffleRuby can be installed to a GraalVM build with the gu command. See graalvm/bin/gu --help for more information.

Running Ruby applications

Run Ruby applications with the standard ruby command:

$ ruby [options] program.rb

Other Ruby commands are available, including gem, irb, rake, rdoc, and ri.

TruffleRuby uses the same options as the standard implementation of Ruby with some additions.

Installing a Ruby gem

$ gem install chunky_png
$ ruby -r chunky_png -e "puts ChunkyPNG::Color.to_hex(ChunkyPNG::Color('mintcream @ 0.5'))"
#f5fffa80

Using Bundler

The only supported version of Bundler at the moment is 1.16.x.

$ gem install bundler -v 1.16.1
$ bundle exec ...

What does TruffleRuby do?

Run idiomatic Ruby code faster

TruffleRuby in conjunction with the GraalVM produces very fast machine code. Traditionally, performance-critical Ruby code has had to be extracted out to language extensions implemented in native code. This approach is cumbersome and error-prone. TruffleRuby on the GraalVM is able to optimize Ruby code. More importantly, it can optimize idiomatic Ruby code.

The following example is taken from the MRI benchmark suite to catch performance regressions in their implementation. It has been slightly modified to work with the benchmark-ips benchmarking harness, rather than MRI’s custom build tool. It exercises the ERB template engine, which is part of Ruby’s standard library and frequently used as a mean of rendering templated documents.

require 'benchmark/ips'
require 'erb'

template = <<TEMPLATE
<html>
  <head> <%= title %> </head>
  <body>
    <h1> <%= title %> </h1>
    <p>
      <%= content %>
    </p>
  </body>

</html>
TEMPLATE

title = "hello world!"
content = "hello world!\n" * 10

src = "def self.render(title, content); #{ERB.new(template).src}; end"
mod = Module.new
mod.instance_eval(src, "(ERB)")

Benchmark.ips do |x|
  x.iterations = 3
  x.report("erb") { mod.render(title, content) }
end
$ gem install benchmark-ips
$ ruby benchmark.rb
MRI 2.3.7 JRuby 9.1.16.0 TruffleRuby
668 K renders/s 877 K renders/s 43 M renders/s

Run Ruby code in parallel

TruffleRuby runs threads in parallel, without a global-interpreter-lock. It includes sophisticated techniques to automatically make objects thread-safe only when they are going to be used by more than one thread, leaving them unsynchronized when used from only one thread to give you parallelism without an overhead when you don’t want to use it.

Boot Ruby applications in less time

A hello-world script in TruffleRuby runs about as fast as MRI, or over ten times faster than JRuby. TruffleRuby can run without a heavy-weight JVM so that you start running your Ruby code almost immediately.

Execute C extensions in a managed environment

TruffleRuby executes C extensions using a managed C interpreter and just-in-time compiler, not by running native code.

Provide new tooling

TruffleRuby includes a built-in debugger that allows you to use Chrome DevTools on your Ruby application.

$ ruby --inspect test.rb
Debugger listening on port 9229.
To start debugging, open the following URL in Chrome:
    chrome-devtools://devtools/bundled/inspector.html?ws=127.0.0.1:9229/ffffffffffffffff-fffffffffffffffff

Add fast and low-overhead interoperability

TruffleRuby allows you to write polyglot programs using the other languages from GraalVM.

High compatibility with the standard implementation of Ruby

TruffleRuby aims to be highly compatible with the standard implementation of Ruby, MRI, version 2.3.7, revision 63024. We test extensively using the MRI test suite and the Ruby Spec project.

Interoperability

GraalVM supports several other programming languages, including JavaScript, R, Python 3, and LLVM. TruffleRuby lets you run code from these languages, and use objects and methods from one language in another.

As an example, we can pass a JSON-encoded string into Graal.js, decode it into a JavaScript object, and then inspect the decoded object in Ruby:

require 'json'

obj = {
  time: Time.now,
  msg: 'Hello World',
  payload: (1..10).to_a
}

encoded = JSON.dump(obj)

js_obj = Polyglot.eval('js', 'JSON.parse').call(encoded)

puts js_obj[:time]
puts js_obj[:msg]
puts js_obj[:payload].join(' ')

We use the --jvm --polyglot option to say we want to run a polyglot program, and -Xsingle_threaded because JavaScript is only compatible with single threaded programs.

$ ruby --jvm --polyglot -Xsingle_threaded test.rb
2018-04-11 15:25:08 -0700
Hello World
1 2 3 4 5 6 7 8 9 10

Compatibility

TruffleRuby aims to be highly compatible with the standard implementation of Ruby, MRI, version 2.3.7, revision 63024.

Our policy is to match the behaviour of MRI, except where we do not know how to do so with good performance for typical Ruby programs. Some features work but will have very low performance whenever they are used and we advise against using them on TruffleRuby if you can. Some features are missing entirely and may never be implemented. In a few limited cases we are deliberately incompatible with MRI in order to provide a greater capability.

In general, we are not looking to debate whether Ruby features are good, bad, or if we could design the language better. If we can support a feature, we will do.

In the future we aim to provide compatibility with extra functionality provided by JRuby, but at the moment we do not.

Identification

TruffleReport defines these constants for identification:

  • RUBY_ENGINE is 'truffleruby'
  • RUBY_VERSION is the compatible MRI version
  • RUBY_REVISION is the compatible MRI version revision
  • RUBY_PATCHLEVEL is always zero
  • RUBY_RELEASE_DATE is the Git commit date
  • RUBY_ENGINE_VERSION is the GraalVM version, or 0.0- and the Git commit hash if your build is not part of a GraalVM release.

Additionally, TruffleRuby defines

  • Truffle.revision which is the Git commit hash

Features entirely missing

Continuations and callcc

Continuations and callcc are unlikely to ever be implemented in TruffleRuby, as their semantics fundamentally do not match the technology that we are using.

Fork

You cannot fork the TruffleRuby interpreter. The feature is unlikely to ever be supported when running on the JVM, but could be supported in the future on the SVM.

Standard libraries

Quite a few of the less commonly used standard libraries are currently not supported, such as fiddle, sdbm, gdbm, tk. It’s quite hard to get an understanding of all the standard libraries that should be available, so it’s hard to give a definitive list of those that are missing.

Features with major differences

Threads run in parallel

In MRI, threads are scheduled concurrently but not in parallel. In TruffleRuby threads are scheduled in parallel. As in JRuby and Rubinius, you are responsible for correctly synchronising access to your own shared mutable data structures, and we will be responsible for correctly synchronising the state of the interpreter.

Fibers do not have the same performance characteristics as in MRI

Most use cases of fibers rely on them being easy and cheap to start up and having low memory overheads. In TruffleRuby we implement fibers using operating system threads, so they have the same performance characteristics as Ruby threads. As with coroutines and continuations, a conventional implementation of fibers fundamentally isn’t compatible with the execution model we are currently using.

Some classes marked as internal will be different

MRI provides some classes that are described in the documentation as being only available on C Ruby (MRI). We implement these classes if it’s practical to do so, but this isn’t always the case. For example RubyVM is not available.

Features with subtle differences

different range of Fixnum

The range of Fixnum is slightly larger than it is in MRI. This won’t be an issue when we support Ruby 2.4, as Integer is unified there.

Command line switches

-y, --yydebug, --dump= are ignored with a warning as they are internal development tools.

-X is an undocumented synonym for -C and we (and other alternative implementations of Ruby) have repurposed it for extended options. We warn if your -X option looks like it was actually intended to be as in MRI.

Setting the process title doesn’t always work

Setting the process title (via $0 or Process.setproctitle in Ruby) is done as best-effort. It may not work, or the title you try to set may be truncated.

Features with very low performance

Keyword arguments

Keyword arguments don’t have very low performance, but they are not optimised as other language features are. You don’t need to avoid keyword arguments, but performance with them may not be what you would hope for.

ObjectSpace

Using most methods on ObjectSpace will temporarily lower the performance of your program. Using them in test cases and other similar ‘offline’ operations is fine, but you probably don’t want to use them in the inner loop of your production application.

set_trace_func

Using set_trace_func will temporarily lower the performance of your program. As with ObjectSpace, we would recommend that you do not use this in the inner loop of your production application.

Backtraces

Throwing exceptions, and other operations which need to create a backtrace, are slower than on MRI. This is because we have to undo optimizations that we have applied to run your Ruby code fast in order to recreate the backtrace entries. We wouldn’t recommend using exceptions for control flow on any implementation of Ruby anyway.

To help alleviate this problem in some cases backtraces are automatically disabled where we dynamically detect that they probably won’t be used. See the -Xbacktraces.omit_unused option.

C Extension Compatibility

Identifiers may be macros or functions

Identifiers which are normally macros may be functions, functions may be macros, and global variables may be macros. This causes problems where they are used in a context which relies on a particular implementation, for example a global variable assigned to an initial value which was a macro, like Qnil, which is a macro that evaluates to a function call in TruffleRuby.

For example, this global variable definition does not work because Qnil expands to be a function call in TruffleRuby:

static VALUE foo = Qnil;

A workaround is to assign foo in your C extension Init_ function, or inside some other function.

Storing Ruby objects in native structures and arrays

You cannot store a Ruby object in a structure or array that has been natively allocated, such as on the stack, or in a heap allocated structure or array.

Simple local variables of type VALUE, and locals arrays that are defined such as VALUE array[n] are an exception and are supported, provided their address is not taken and passed to a function that is not inlined.

void *rb_tr_handle_for_managed(VALUE managed) and VALUE rb_tr_managed_from_handle(void *native) may help you work around this limitation. Use void* rb_tr_handle_for_managed_leaking(VALUE managed) if you don’t yet know where to put a corresponding call to void *rb_tr_release_handle(void *native). Use VALUE rb_tr_managed_from_handle_or_null(void *native) if the handle may be NULL.

Mixing native and managed in C global variables

C global variables can contain native data or they can contain managed data, but they cannot contain both in the same program run. If you have a global you assign NULL to (NULL being just 0 and so a native address) you cannot then assign managed data to this variable.

Variadic functions

Variadic arguments of type VALUE that hold Ruby objects can be used, but they cannot be accessed with va_start etc. You can use void *polyglot_get_arg(int i) instead.

Pointers to VALUE locals and variadic functions

Pointers to local variables that have the type VALUE and hold Ruby objects can only be passed as function arguments if the function is inlined. LLVM will never inline variadic functions, so pointers to local variables that hold Ruby objects cannot be passed as variadic arguments.

rb_scan_args is an exception and is supported.

rb_scan_args

rb_scan_args only supports up to ten pointers.

RDATA

The mark function of RDATA and RTYPEDDATA is never called.

Ruby objects and truthiness in C

All Ruby objects are truthy in C, except for Qfalse, the Fixnum value 0, and the Float value 0.0. The last two are incompatible with MRI, which would also see these values as truthy.

Compatibility with JRuby

Ruby to Java interop

Calling Java code from Ruby (normal Java code, not JRuby’s Java extensions which are covered below) is in development but not ready for use yet. We aim to provide the same interface as JRuby does for this functionality.

Java to Ruby interop

Calling Ruby code from Java is supported by the Graal-SDK polyglot API.

Java extensions

Using Java extensions written for JRuby is not supported. We could apply the same techniques as we have developed to run C extensions to this problem, but it’s not clear if this will ever be a priority.

Compatibility with Rubinius

We do not have any plans at the moment to provide support for Rubinius’ extensions to Ruby.

Features not yet supported in native configuration

Java interop

Running TruffleRuby in the native configuration is mostly the same as running on the JVM. There are differences in resource management, as both VMs use different garbage collectors. But, functionality-wise, they are essentially on par with one another. The big difference is support for Java interop, which currently relies on reflection. TruffleRuby’s implementation of Java interop does not work with the SVM’s limited support for runtime reflection.

Spec Completeness

‘How many specs are there’ is not a question with an easy precise answer. The three numbers listed below for each part of the specs are the number of expectations that the version of MRI we are compatible with passes, then the number TruffleRuby passes, and then the TruffleRuby number as a percentage of the MRI number. This is run on macOS. The numbers probably vary a little based on platform and configuration. The library and C extension specs are quite limited so may be misleading.

  • Language: 3913, 3903, 99%
  • Core: 176111, 169117, 96%
  • Library (:library and :openssl on TruffleRuby): 20820, 16934, 81%
  • C extensions: 1679, 1627, 97%

Compatibility with gems

You can use the compatibility checker to find whether the gems you’re interested in are tested on GraalVM, whether the tests pass successfully and so on. Additionally, you can drop your Gemfile.lock file into that tool and it’ll analyze all the gems you’re using at once. Note that the processing is done on the client-side, so no information is uploaded to any servers.

Options

TruffleRuby has the same command line interface as MRI 2.3.7.

Usage: truffleruby [switches] [--] [programfile] [arguments]
  -0[octal]       specify record separator (\0, if no argument)
  -a              autosplit mode with -n or -p (splits $_ into $F)
  -c              check syntax only
  -Cdirectory     cd to directory before executing your script
  -d, --debug     set debugging flags (set $DEBUG to true)
  -e 'command'    one line of script. Several -e's allowed. Omit [programfile]
  -Eex[:in], --encoding=ex[:in]
                  specify the default external and internal character encodings
  -Fpattern       split() pattern for autosplit (-a)
  -i[extension]   edit ARGV files in place (make backup if extension supplied)
  -Idirectory     specify $LOAD_PATH directory (may be used more than once)
  -l              enable line ending processing
  -n              assume 'while gets(); ... end' loop around your script
  -p              assume loop like -n but print line also like sed
  -rlibrary       require the library before executing your script
  -s              enable some switch parsing for switches after script name
  -S              look for the script using PATH environment variable
  -T[level=1]     turn on tainting checks
  -v, --verbose   print version number, then turn on verbose mode
  -w              turn warnings on for your script
  -W[level=2]     set warning level; 0=silence, 1=medium, 2=verbose
  -x[directory]   strip off text before #!ruby line and perhaps cd to directory
  --copyright     print the copyright
  --enable=feature[,...], --disable=feature[,...]
                  enable or disable features
  --external-encoding=encoding, --internal-encoding=encoding
                  specify the default external or internal character encoding
  --version       print the version
  --help          show this message, -h for short message
Features:
  gems            rubygems (default: enabled)
  did_you_mean    did_you_mean (default: enabled)
  rubyopt         RUBYOPT environment variable (default: enabled)
  frozen-string-literal
                  freeze all string literals (default: disabled)

TruffleRuby also reads the RUBYOPT environment variable.

Unlisted Ruby switches

MRI has some extra Ruby switches which are aren’t normally listed.

  -U              set the internal encoding to UTF-8
  -KEeSsUuNnAa    sets the source and external encoding
  -y, --ydebug    debug the parser
  -Xdirectory     the same as -Cdirectory
  --dump=insns    print disassembled instructions

TruffleRuby-specific switches

Beyond the standard Ruby command line switches we support some additional switches specific to TruffleRuby.

TruffleRuby switches:
  -Xlog=severe,warning,performance,info,config,fine,finer,finest
                  set the TruffleRuby logging level
  -Xoptions       print available TruffleRuby options
  -Xname=value    set a TruffleRuby option (omit value to set to true)

As well as being set at the command line, options, except for log, can be set using --ruby.option= in any GraalVM launcher. For example --ruby.inline_js=true. They can also be set as JVM system properties, where they have a prefix polyglot.ruby.. For example -J-Dpolyglot.ruby.inline_js=true, or via any other way of setting JVM system properties. Finally, options can be set as Graal-SDK polyglot API configuration options.

The priority for options is the command line first, then the Graal-SDK polyglot API configuration, then system properties last.

The logging level is not a TruffleRuby option like the others and so cannot be set with a JVM system property. This is because the logger is once per VM, rather than once per TruffleRuby instance, and is used to report problems loading the TruffleRuby instance before options are loaded.

TruffleRuby-specific options, as well as conventional Ruby options, can also bet set in the TRUFFLERUBYOPT environment variable.

-- or the first non-option argument both stop processing of Truffle-specific arguments in the same way it stops processing of Ruby arguments.

JVM- and SVM-specific switches

If you are running TruffleRuby on a JVM or the GraalVM, we additionally support passing options to the JVM using either a -J- or --jvm. prefix. For example -J-ea. -J-classpath and -J-cp also implicitly take the following argument to be passed to the JVM. -J-cmd print the Java command that will be executed, for debugging.

JVM switches:
  --jvm.arg,         -J-arg           pass arg to the JVM
  --jvm.Dname=value, -J-Dname=value   set a system property

-- or the first non-option argument both stop processing of JVM-specific arguments in the same way it stops processing of Ruby arguments.

TruffleRuby also supports the JAVA_HOME, JAVACMD and JAVA_OPTS environment variables when running on a JVM (except for JAVACMD on the GraalVM).

SVM-specific switches

The SVM supports --native.D for setting system properties and --native.XX:arg for SVM options.

Native switches:
  --native.XX:arg       pass arg to the SVM
  --native.Dname=value  set a system property

Determining the TruffleRuby home

TruffleRuby needs to know where to locate files such as the standard library. These are stored in the TruffleRuby home directory. The TruffleRuby option home has priority for setting the home directory. Otherwise it is set automatically to the directory containing the TruffleRuby JAR file, if TruffleRuby is running on a JVM.

Operations Manual

If you are attempting to experiment with deploying TruffleRuby to production we would encourage you to contact us so we can help you understand what is possible at the moment and to help solve any issues for you.

TruffleRuby configurations

There are two main configurations of TruffleRuby - native and JVM. It’s important to understand the different configurations of TruffleRuby, as each has different capabilities and performance characteristics. You should pick the execution mode that is appropriate for your application.

When distributed as part of GraalVM, TruffleRuby by default runs in the native configuration. In this configuration, TruffleRuby is ahead-of-time compiled to a standalone native executable. This means that you don’t need a JVM installed on your system to use it. The advantage of the native configuration is that it starts about as fast as MRI, it may use less memory, and it becomes fast in less time than the JVM configuration. The disadvantage of the native configuration is that you can’t use Java tools like VisualVM, you can’t use Java interoperability, and peak performance may be lower than on the JVM. The native configuration is used by default, but you can also request it using --native. To use polyglot programming with the native configuration, you need to use the --polyglot flag.

TruffleRuby can also be used in the JVM configuration, where it runs as a normal Java application on the JVM, as any other Java application would. The advantage of the JVM configuration is that you can use Java interoperability, and peak performance may be higher than the native configuration. The disadvantage of the JVM configuration is that it takes much longer to start and to get fast, and may use more memory. The JVM configuration is requested using --jvm.

If you are running a short-running program you probably want the default, native, configuration. If you are running a long-running program and want the highest possible performance you probably want the JVM configuration, by using --jvm.

Getting the best startup time performance

To get the best startup time performance in most cases you want to use the native configuration, which is the default.

Getting the lowest memory footprint

To get the lowest memory footprint you probably initially want to use the native configuration, but as you get a larger working set of objects you may find that the simpler garbage collector and current lack of compressed ordinary object pointers (OOPS) actually increases your memory footprint and you’ll be better off with the JVM configuration using --jvm to reduce memory use.

Getting the best peak performance from TruffleRuby

To get the best peak performance from TruffleRuby for longer-running applications we would in most cases recommend the JVM configuration with --jvm.

However to reach this peak performance you need to warm-up TruffleRuby, as you do with most heavily optimising virtual machines. This is done by running the application under load for a period of time. If you monitor the performance (by measuring operation time, or response time) you will see it reduce over time and then probably stabilise.

Tuning TruffleRuby

To tune TruffleRuby you will need to consider the options of either your JVM or the SubstrateVM, and then Truffle, and Graal.

TruffleRuby has a large set of options, which you can see with the -Xoptions flag.

Logging

Ruby application logging and warning works as in the standard implementation of Ruby.

For logging of TruffleRuby internals, standard Java logging is used. The logging level can be set with -Xlog=info, =finest, or so on. An additional logging level, -Xlog=performance includes warnings about operations that may be slow.

For advanced configuration, write a Java logging configuration file and load it using -J-Djava.util.logging.config.file=logging.properties. You can use this to log to a file and to set the level.

org.truffleruby.handlers=java.util.logging.FileHandler
java.util.logging.FileHandler.pattern=ruby.log
org.truffleruby.level=CONFIG