Table of contentsClick link to navigate to the desired location
This content has been automatically translated from Ukrainian.
First, we need to understand what JIT, YJIT are and whether we need them. In this note, I will describe the process of updating Ruby (since YJIT is installed with Ruby only when using a flag). We will test the speed of Ruby and Ruby with YJIT.
What is JIT and YJIT?
JIT (Just-In-Time) compilation in Ruby is a technology that transforms Ruby bytecode into native machine code directly during program execution. Using JIT can significantly improve the performance of program execution since native machine code runs faster than interpreted bytecode. Ruby uses the YARV (Yet Another Ruby VM) virtual machine, which transforms the written Ruby code into bytecode before executing it. Adding JIT to this process allows for dynamic compilation of some parts of the code on the fly, speeding up code execution and reducing server load.
YJIT is a new JIT compiler that has been built into Ruby since version 3.1. YJIT uses so-called "lazy" JIT compilation, where compilation is performed only for "hot" execution paths that are executed frequently. This reduces the cost of compiling code that is executed rarely and provides a performance boost where it is most needed.
YJIT increases the execution speed of programs without the need for significant changes to the code itself. YJIT is the result of an attempt to optimize Ruby's performance using JIT compilation while maintaining stability and compatibility with existing programs.
Checking if YJIT is enabled
My local setup is macOS, rbenv, and brew. Locally, we check the Ruby version and whether YJIT is enabled:
Terminal:
ruby -v ruby 3.2.1
Rails Console:
irb(main):003:0> RubyVM::YJIT.enabled?
(irb):3:in `<main>': uninitialized constant RubyVM::YJIT (NameError)
RubyVM::YJIT.enabled?
^^^^^^
That is, currently Ruby does not have YJIT.
Measuring the execution speed of Ruby without YJIT
Let's create a script that will calculate the Fibonacci sequence. I did a bit of Googling and asked ChatGPT how to do simple benchmarking, and this is the most common option.
We create a file benchmark.rb with the code:
require 'benchmark'
def fibonacci(n)
return n if n <= 1
fibonacci(n - 1) + fibonacci(n - 2)
end
puts Benchmark.measure {
fibonacci(40)
}
We run:
ruby ~/Desktop/benchmark.rb
And my result currently (without YJIT):
8.309469 0.000711 8.310180 (8.320920)
Installing Ruby with YJIT
Currently, I have Ruby 3.2.1 (rbenv). I need to install the same version, but with YJIT. So let's get started.
Disclaimer: Everyone has their own setup. The example provided is just my case.
The official documentation states that YJIT requires:
- A C compiler such as GCC or Clang
- GNU Make and Autoconf
- The Rust compiler rustc and Cargo (if you want to build in dev/debug mode)
- The Rust version must be >= 1.58.0.
From all this, I only need to install Rust (Cool, right? The Rust programming language is used as a compiler).
We install Rust using brew:
brew install rust
This may take quite a long time (~10min).
In .zshrc, you need to add the following flag:
export RUBY_CONFIGURE_OPTS="--enable-yjit"
Without the flag, Ruby will be installed without YJIT (by default, it is disabled). This is an important point.
Restart the terminal to pull in RUBY_CONFIGURE_OPTS.
We install Ruby (technically, rbenv install 3.2.1 will run with the flag --enable-yjit):
\W $ rbenv install 3.2.1 rbenv: /Users/user/.rbenv/versions/3.2.1 already exists continue with installation? (y/N) y
After installation, we check if YJIT works and benchmark the execution of the benchmark.rb file.There is a nuance here. YJIT, even if installed with Ruby, is disabled.
Add this flag to run Ruby with YJIT:
export RUBY_YJIT_ENABLE=true
Then we should see +YJIT when checking ruby -v
ruby -v ruby 3.2.1 (2023-02-08 revision 31819e82c8) +YJIT [x86_64-darwin23]
irb RubyVM::YJIT.enabled? => true
YJIT is working. Next, we test.
ruby ~/Desktop/benchmark.rb
And we get:
1.624351 0.000245 1.624596 (1.626251)
So the difference in execution speed of the script is 5.11 times:
8.309469/1.624351=5.1155624616
Technically, this is quite a large and significant upgrade in Ruby's speed. By enabling just one option, at the compilation level, we achieve a very powerful optimization. YJIT is already production-ready and can be used on production servers, for example, for Ruby on Rails applications. For instance, Heroku offers to enable YJIT with just one command:
heroku config:set RUBYOPT="--enable-yjit"
This post doesn't have any additions from the author yet.