9 min read
Just as there are many shades of the ruby gem, there are multiple implementations of the Ruby interpreter.
The most commonly used Ruby interpreter is the reference implementation, Ruby MRI, developed in C by the creator of Ruby (Yukihiro Matsumoto) and the Ruby core team.
Our Ruby on Rails Hiring Guide mentions that some of the drawbacks in Rails can potentially be solved or avoided by using an alternate Ruby interpreter. This article showcases the various existing Ruby interpreter implementations and runtimes available today, discussing the advantages and disadvantages of each.
Ruby Version History (and how it affects alternative implementations)
Sadly, there is no equivalent of the Python Language Reference for Ruby (ISO/IEC 30170:2012 describes Ruby 1.8 / Ruby 1.9, but no corresponding spec exists for Ruby 2.x). In the absence of any such language specification, Ruby implementors typically rely instead on the community-driven RubySpec that specifies expected behaviors of the Ruby language through tests that can be run in any Ruby interpreter. RubySpec is therefore used by Ruby implementors to verify the behavioral compliance of their Ruby implementations with the de facto standard.
Due to the lack of a formal specification, new releases of Ruby often simply correspond to new releases of the Ruby MRI. It’s worth noting that there is an open issue that discusses a design process for decoupling Ruby (the language) from the Ruby MRI.
Given the current tight coupling, though, between the Ruby language and the MRI reference implementation, developers of alternate Ruby implementations sometimes struggle to keep up with the language changes introduced in each new MRI release.
Never was this harder than in the transition between Ruby 1.8 and Ruby 1.9. In 2007, in an effort to clean up and consolidate Ruby’s syntax (as the language had evolved in the decade since Ruby 1.0’s release), the Ruby core team released Ruby 1.9.0, a version which introduced many backward incompatibilities into the language. As a result, not all Ruby implementations invested the effort necessary to make the syntactical jump from 1.8 to 1.9. As such, there are several 1.8-based Ruby implementations that aren’t used by the community anymore, but which you may still find online or talked about by old Ruby hands.
A new version of Ruby MRI is released every Christmas, following a semantic versioning principle. Ruby 2.0 (released 2013) and 2.1 (released 2014) each introduced additional language features that Ruby developers can take advantage of, without forfeiting backward compatibility with Ruby 1.9.
Why use an alternative Ruby implementation? What’s wrong with MRI?
For a long time many Ruby on Rails developers used Ruby Enterprise Edition (REE) instead of MRI, taking advantage of the better memory management techniques in REE as compared to the MRI version at that time. (REE was subsequently discontinued though in 2012.)
While MRI is the default Ruby implementation, it’s not the necessarily the correct choice for all environments and scenarios. For example, MRI’s concurrency support is inferior to that of JRuby or Rubinius. Also, although MRI’s memory and garbage collection schemes are constantly improving, they still do have some issues.
The survey of Ruby implementations that follows is intended to assist you in selecting the interpreter best suited to your project’s operational goals and constraints.
Matz’s Ruby Interpreter (MRI) / CRuby
Written in C by the Ruby core team lead by Yukihiro Matsumoto (“Matz”, the creator of Ruby), MRI is the reference implementation of Ruby which serves as the de facto standard. If an OS vendor includes a version of Ruby as part of the OS installed software, for example, it’s usually the MRI version. MRI benefits from more paid core team members than any other Ruby implementation, as well as contributed resources dedicated from people or companies that want to improve the Ruby ecosystem.
A new version of Ruby MRI – often implementing new language features, in addition to standard library changes – is released every Christmas. Features are implemented in Ruby MRI first, usually based on discussions on the Ruby core developer mailing list. Other Ruby implementations lag behind, in some cases even by years.
JRuby is a version of Ruby implemented on top of the Java Virtual Machine (JVM). As it becomes popular for languages beyond just Java to run on top of the JVM (I’m looking in your direction, Clojure and Scala), a JVM-based Ruby implementation is likely to gain in popularity.
Ruby in the JVM also means that Ruby can run anywhere Java can run (like Android phones, using Ruboto for example). Additionally, thanks to the interoperability of the JVM, JRuby code can make use of the Java platform, including both standard and 3rd party libraries.
JRuby is also useful for bringing a Rails-based solution into a Java-only deployment environment, packaging up the Rails app as a
.war file to deploy to a Tomcat container, or as a Java applet running as part of your web front-end, for example.
For those not accustomed to the JVM, though, JRuby brings standard JVM-related issues such as slow startup of the Ruby interpreter, debugging CLASSPATH issues if you’re using 3rd party Java libraries, larger memory usage, and the fact that now your code needs to be written with thread safety considerations in mind.
Also, some features of Ruby (C APIs, and one of Ruby’s powerful introspection tools, the ObjectSpace module) are not implemented in JRuby.
All that said, the advantages of using the JVM may outweigh the disadvantages for certain situations or projects. The JVM allows for many performance optimizations, such as turning on the JIT compiler, or using native Java objects and APIs.
As an example of a compelling JRuby use case, a former co-worker of mine once had a CPU-intensive problem that he initially solved with threads in Ruby 1.9.3. When he switched to JRuby and used Java’s
java.util.concurrent.Executors, he saw a performance improvement of multiple orders of magnitude (tens of thousands of times faster) for this operation. See his experiment here.
Rubinius is an implementation of Ruby that implements a generic runtime for dynamic languages on top of a Low Level Virtual Machine (LLVM). Using this infrastructure and JIT compiler technology, Rubinius can often run Ruby code with less overhead than MRI.
Rubinius is also built using as much Ruby as possible to make development of the interpreter / runtime faster and easier.
Fun fact: RubySpec initially came into being in the process of implementing Rubinius.
Like JRuby, Rubinius includes a JIT compiler, better memory management, and a more mature virtual machine than Ruby MRI. However, unlike JRuby, Rubinius supports Ruby C libraries and the underpinnings of Rubinius are written in C++, not Java.
Rubinius may be a good middle ground when you need high performance on your Rails servers without the learning curve or other disadvantages of JRuby.
mruby is designed to be an embeddable version of Ruby (supporting Ruby 1.9.3). With mruby, you can offer Ruby as a scripting / automation language in native applications, use it for game scripting, and even for programming microcontroller boards like the Raspberry Pi.
If your platform has severe resource constraints, mruby may be just the Ruby interpreter for you. mruby is also being used to:
- Build iOS apps (as a competitor to RubyMotion, discussed below)
- Embed Ruby into iOS apps, for development speed
- Offer end users an embedded scripting language for automation purposes
With the Internet of Things becoming more and more of a reality, home automation coming into its own, and extremely portable (and relatively powerful) computers being more commonplace, the landscape of target platforms to support is becoming increasingly diverse. mruby helps make it possible to do so with the same productive language one would use on the desktop.
Opal runs standalone or can be used as part of the Rails asset pipeline (e.g., to automatically transpile your
RubyMotion is a commercial product that enables you to write Cocoa native apps in Ruby. RubyMotion 2.0 allows you to write iOS and Mac OS X apps in Ruby, and RubyMotion 3 promises to bring this same support to Android.
RubyMotion implements version 1.9 of the Ruby language.
Over the years since Ruby was first introduced, some of the Ruby implementations that have come into being have been abandoned or discontinued, such as:
- Ruby Enterprise Edition (REE). REE was a fork of MRI 1.8 from the folks at Phusion Passenger that implemented many memory and garbage collection improvements for web developers. For several years, it was the default Ruby implementation deployed for production Rails sites. It was never updated for Ruby 1.9 or Ruby 2.0, though, and was ultimately discontinued in 2012.
- IronRuby. IronRuby is Ruby implemented on top of Microsoft .NET, written in C#, and for a while the project was funded by Microsoft. Abandoned in 2011, IronRuby last supported Ruby 1.8.6.
There are a wide variety of runtimes and interpreters to pick from on the Ruby landscape. For most Ruby projects, the Ruby reference implementation (Ruby MRI) remains the interpreter of choice. However, alternate Ruby implementations may very well be the right choice for your project, depending on your functional and technical goals and constraints.
In its role as the reference implementation of Ruby, MRI gets new language features faster, has good enough concurrency and memory stories (that are only getting better), and has the widest compatibility with gems (some partially written in C). All in all, MRI is a solid, dependable choice for general purpose Ruby code.
For larger, enterprise deployments, or for situations where you either need to interact with Java code (or other JVM languages) or need highly evolved concurrency patterns, JRuby is a compelling option.
With a wide variety of Ruby runtimes and interpreters to pick from, Ruby shows itself to be a flexible language, useful for a wide array of computing environments, ranging from a corporate big iron Java deployment shop, to software that controls that stoplight in your office you hooked your Raspberry Pi into last weekend. Picking the right tool for the right purpose is essential, yes, but hopefully this article has shown you that Ruby is so much more than the default Ruby interpreter that came with your OS.
The world of Ruby is greatly enhanced by alternative Ruby implementation teams working with the core Ruby MRI team as changes to the language are proposed. They add diversity to the Ruby implementation community, adding their hard won Ruby implementation experiences and their own perspectives on features going into the language. Ruby enthusiasts collectively owe these teams a great debt of gratitude. Kudos to them for their efforts!