JIT Compilers for Ruby and Rails: An Overview | AppSignal Blog (2024)

A program is compiled at runtime using a different method from pre-execution compilation. This process is known as just-in-time compilation or dynamic translation.

In this post, we'll look at why JIT compilation can be a good choice for your Ruby on Rails app, before looking at some of the options available (YJIT, MJIT, and TenderJIT) and how to install them.

But first: how does JIT compilation work?

How a JIT Compiler Works

Just-in-time compilation is a method of running computer code that requires compilation while running a program.

This could entail translating the source code, but it's most frequently done by converting the bytecode to machine code,which is then run directly.

The code being executed is often continuously analyzed by a system using a JIT compiler. This identifies sections of code where the benefit of compilation or recompilation (in terms of speed) outweighs the cost.

Benefits of JIT Compilation for Ruby

JIT compilation combines some of the benefits (and shortcomings) of the two conventional methods for converting programs into machine code:interpretation and ahead-of-time compilation (AOT).

Roughly speaking, it combines the flexibility of interpretation with the speedof generated code, and the additional overhead of compiling and linking (not just interpreting).

JIT compilation is a type of dynamiccompilation that enables adaptive optimization techniques, including dynamic recompilation and speed-ups tailored to certain microarchitectures. Due to a runtime system's ability to handle late-bound data types and impose security guarantees, dynamic programming languages like Ruby are particularlywell-suited for interpretation and JIT compilation.

An optimizing compiler like GCC can more efficiently optimize instructions — a significant advantage of adoptinga register-oriented architecture. Compilers operate on intermediate representation with register-based architecture.

Once your instructions reach an intermediate representation during compilation, GCC does additional passes to speed up the CPU's execution of your instructions.

JIT Compilers for Ruby: YJIT, MJIT, and TenderJIT

Now let's explore the different JIT compilers available for Ruby — YJIT, MJIT, and TenderJIT — and how you can set them up.

MJIT (Method-based Just-in-time Compiler) for Ruby

Vladimir Makarov implemented MJIT, and it was the first compiler methodology implemented in Ruby based on the C language.It works with Ruby 2.6, uses YARV instructions, and compiles instructions often used in binary code.

For programs that are not input/output-bound, MJIT enhances performance.

YJIT is better than this original C-based compiler in terms of performance. Ruby 3's JIT is the quickest JIT that MRI has ever had, made possible by the excellent work of MJIT.

How to Use MJIT

To use MJIT, you can enable the JIT in Ruby 2.6 and with the --jit option.

If you skip this part, MJIT will show an error.

sh

clang: error: cannot specify -o when generating multiple output filesMJIT warning: Making precompiled header failed on compilation. Stopping MJIT worker...MJIT warning: failed to remove "/var/folders/3d/fk_588wd4g12syc56pjqybjc0000gn/T//_ruby_mjit_hp25992u0.h.gch": No such file or directorySuccessful MJIT finish

A collection of JIT-specific settings included in Ruby 2.6 helps us understand how it functions. Run ruby --help to view these options.

In short, MJIT executes in a different thread and is asynchronous. It will begin just-in-time compilation following the first five runs of a calculation.

YJIT for Ruby on Rails

A recent JIT compiler called YJIT was released with Ruby 3.1. It promises a lot of improvements and better performance.Still a work-in-progress project designed by Shopify with experimental results, it must be used with caution, especially on larger applications.

With that in mind, YJIT enhances the performance of Ruby on Rails applications. The majority of real-world software benefitsfrom the fast warm-up and performance enhancements provided by the YJIT basic block versioning JIT compiler.

A JIT compiler will be gradually built into CRuby as part of the YJIT project, eventually replacing the interpreter for most of the code execution.

Official benchmarks — see 'YJIT: Building a New JIT Compiler for CRuby' — show that YJIT improved performance over the default CRuby interpreter by:

  • 20% on railsbench
  • 39% on liquid template rendering
  • 37% on activerecord

However:

Only about 79% of instructions in railsbench are executed by YJIT,and the rest run in the default interpreter.

Source: YJIT: Building a New JIT Compiler for CRuby

This means that a lot still needs to be done to improve YJIT's current results.

Even so, YJIT performs at least as well as the interpreter on every benchmark, even on the hardestones, and reaches near-peak performance after just one iteration of every benchmark.

How to Use YJIT

Note: YJIT is currently limited to macOS and Linux on x86-64 platforms. Also, as mentioned, YJIT is not recommended for large applications (yet).

YJIT is disabled by default. If you want to enable it, first specify the --yjit command-line option.

You need to check if it is installed, so run ruby --enable-yjit -v. If warning: unknown argument for --enable: yjit'` shows up, you have to install it.

Then open irb and set RUBY_YJIT_ENABLE=1. You can exit and now, you're ready to use YJIT. The command ruby --enable-yjit -v must return something like ruby 3.1.0p0 (2021-12-25 revision fb4df44d16) [arm64-darwin21]

TenderJIT

With a design largely based off YJIT, TenderJIT is an experimental JIT compiler for Ruby. What's different about TenderJIT is that it's written in pure Ruby.

This is a demo project and the aim is to ship it as a gem. In the meantime, you can experiment with it, but bear in mind it's still a work in progress. Ruby 3.0.2 or later is required for TenderJIT.

How to Use TenderJIT

TenderJIT does not currently do method compilation automatically. To compile a method, you must manually configure TenderJIT.

Clone the repository and run the following commands:

sh

$ bundle install$ bundle exec rake test

You must set it manually on your code:

rb

require "tenderjit" def your_method ...end jit = TenderJIT.newjit.compile(method(:your_method))

Each YARV instruction in the target method is read by TenderJIT, which then transforms it into machine code.

For more examples with TenderJIT, check one of these videos:A JIT compiler for Ruby with Aaron Patterson andHacking on TenderJIT!

Wrapping Up

In this post, we've taken a quick look at three JIT compilers for Ruby — MJIT, YJIT, and TenderJIT — and how to set them up. Each of the options is experimental and comes with its own limitations.

However, YJIT is the most mature at the moment, and it has the biggest potentialto grow and scale. It demonstrates better performance over the other Ruby JITs, was developed with Ruby 3.1.0, and is quickly becoming an important part of CRuby.

Check out this post if you want to build your own compiler for Ruby.

Happy coding!

P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, subscribe to our Ruby Magic newsletter and never miss a single post!

JIT Compilers for Ruby and Rails: An Overview | AppSignal Blog (2024)

References

Top Articles
Latest Posts
Article information

Author: Errol Quitzon

Last Updated:

Views: 5891

Rating: 4.9 / 5 (79 voted)

Reviews: 94% of readers found this page helpful

Author information

Name: Errol Quitzon

Birthday: 1993-04-02

Address: 70604 Haley Lane, Port Weldonside, TN 99233-0942

Phone: +9665282866296

Job: Product Retail Agent

Hobby: Computer programming, Horseback riding, Hooping, Dance, Ice skating, Backpacking, Rafting

Introduction: My name is Errol Quitzon, I am a fair, cute, fancy, clean, attractive, sparkling, kind person who loves writing and wants to share my knowledge and understanding with you.