Schrodinger's Compiler or The Current State of Ruby YJIT

YJIT - Yet Another Ruby JIT
Ruby v3.1 introduced an experimental just-in-time compiler, developed by Shopify, affectionately named YJIT. Fast forward to Ruby’s traditional Christmas release in December 2022 and v3.2 was announced, removing the “experimental” label from YJIT and declaring it production-ready.
Between Ruby 3.1 and 3.2 the YJIT compiler was rewritten from C to Rust. The benchmarks look very promising - especially for longer running processes - averaging 41% faster with only 33% as much memory overhead. This change in technology brought an incremental increase in complexity, including some hoops to jump through in order to take full advantage of this new mode.
Is YJIT installed by default? Maybe.
When building and installing Ruby from source, if
Rust v1.58 or newer is found on your path and
you’re on a supported platform (macOS or Linux running x86-64
or
arm64
/aarch64
), YJIT will automatically be compiled:
checking for rustc... rustc
checking whether rustc works for YJIT... yes
Is YJIT enabled by default? No.
The YJIT docs mention it is disabled by default. Depending on your use case, you may or may not want YJIT enabled during runtime. As discussed on this Mastodon thread, YJIT optimization only occurs after a method has been executed 30 times:
@solnic @joeldrapper By default, YJIT optimises a method on the 30th time you call it. You can change that with a command line param. But if your methods aren’t run very many times, YJIT is often not a help. So we don’t recommend it for unit tests, as a rule.
Source: https://www.solnic.dev/i/92815145/ruby-with-yjit#%C2%A7ruby-with-yjit
That means it’s probably overhead on something like a test suite, but as always,
your mileage may vary. To enable it at runtime, we have a couple of options. You can pass the --yjit
argument to ruby
cli or set one of two environment variables, RUBY_YJIT_ENABLE=1
or
RUBYOPT=--yjit
If YJIT was not installed but you try to run Ruby with YJIT enabled, you’ll see the following warning:
ruby --yjit -v
# => ruby: warning: Ruby was built without YJIT support.
# => You may need to install rustc to build Ruby with YJIT.
# => ruby 3.2.0 (2022-12-25 revision a528908271) [aarch64-linux]
Otherwise, you’ll see a +YJIT indicator in the version string:
ruby --yjit -v
# => ruby 3.2.0 (2022-12-25 revision a528908271) +YJIT [x86_64-linux]
You can also interrogate the RubyVM
object at runtime to see if YJIT is
enabled:
puts RubyVM::YJIT.enabled?
# => true
What about Docker?
At the time of this writing, the official Docker Ruby images from https://hub.docker.com/_/ruby only included support for YJIT in the Alpine flavor of Linux, due to outdated versions of Rust included in the Debian package repositories. 😔
ℹ️ Update: 2023 January 13
Supported Rust versions have now been merged into the official Docker Ruby images making YJIT supported in the following distros:
OS | Image |
---|---|
Alpine 3.16 | ruby:3.2.0-alpine3.16 |
Alpine 3.17 | ruby:3.2.0-alpine3.17 |
Debian Buster | ruby:3.2.0-buster |
Debian Buster (Slim) | ruby:3.2.0-slim-buster |
Debian Bullseye | ruby:3.2.0-bullseye |
Debian Bullseye (Slim) | ruby:3.2.0-slim-bullseye |
I also learned recently that https://ruby-lang.org provide their own Docker images, which add support for YJIT to the following distros:
OS | Image |
---|---|
Ubuntu Focal | rubylang/ruby:3.2.0-dev-focal |
Ubuntu Focal (Slim) | rubylang/ruby:3.2.0-focal |
Ubuntu Jammy | rubylang/ruby:3.2.0-dev-jammy |
Ubuntu Jammy (Slim) | rubylang/ruby:3.2.0-jammy |