skip to content

In Ruby 4.0, what is ZJIT, how do the --zjit and --jit flags relate to YJIT, and should ZJIT run in production?

level: middleimportance: nice to knowfreq 20%

answer

  1. new and experimental in 4.0
  2. method-based, profile-guided
  3. --zjit or RubyVM::ZJIT.enable
  4. --jit means the build's default
  5. one JIT per process

basics

~20 s

ZJIT is an experimental method-based JIT new in Ruby 4.0, enabled with --zjit or RubyVM::ZJIT.enable; it beats the interpreter but not yet YJIT and is not advised for production. --jit enables the build's default JIT, YJIT when present.

solid answer

~40 s

Ruby 4.0 ships a second JIT, **ZJIT**. Where YJIT compiles lazily block by block, ZJIT compiles whole methods, guided by type profiles the interpreter gathers first. You enable it with `--zjit` (or `--zjit-disable` plus `RubyVM::ZJIT.enable` later); building it needs Rust 1.85 or newer. The 4.0 release notes say it is faster than the interpreter but not yet as fast as YJIT, encourage experiments and advise against production for now; the stated goal is production readiness in 4.1. Only one JIT can run in a process: passing both flags makes Ruby warn and exit, and `RubyVM::YJIT.enable` returns `false` while ZJIT is on. `--jit` means "the default JIT for this build" - `--yjit` when YJIT is compiled in, otherwise `--zjit`. For production on 4.0, YJIT is the choice.

code

bash · 4 lines
bash
ruby --zjit -e 'p RubyVM::ZJIT.enabled?'          # true (experimental)
ruby --jit -e 'p RubyVM::YJIT.enabled?'           # true on a build with YJIT
ruby --zjit -e 'p RubyVM::YJIT.enable'            # warns, then false
ruby --yjit --zjit -e 'p 1'                       # warns "Only one JIT..." and exits

go deeper

for a junior

Recall that Ruby 4.0 has two JITs, that YJIT is the production one and ZJIT is experimental, and that both are off by default.

for a middle

Explain method-based versus block-based compilation, the --zjit and --jit flags, and why only one JIT may be active.

for a senior

Show you would trial ZJIT outside production, name --yjit explicitly in deploy scripts, and track the 4.1 target without assuming it.

for a principal

Frame JIT choice as a release-cadence decision: when an experimental compiler becomes default-worthy, and what evidence the team needs first.

## Two JITs in one release Ruby 4.0 contains two just-in-time compilers, both written in Rust and both **disabled by default**: - **YJIT** - no longer experimental since Ruby 3.2, and the JIT to use in production. It compiles **lazily, one basic block at a time**, using basic block versioning to specialise code for the types it meets. - **ZJIT** - new in Ruby 4.0 and **experimental**. It is a **method-based** JIT: it compiles whole methods, using profile information the interpreter records first to guide optimisation. ## What the 4.0 release notes say about ZJIT 1. It can be enabled with `--zjit` or by calling `RubyVM::ZJIT.enable`. 2. Building Ruby with ZJIT support needs Rust 1.85.0 or later. 3. As of 4.0.0 it is faster than the interpreter but **not yet as fast as YJIT**. 4. The Ruby team encourages experimenting with it but **advises against deploying it in production for now**. 5. The goal is for it to be faster than YJIT and production-ready in Ruby 4.1 - a goal, not a shipped feature. ## ZJIT's switches | Option | Default | Purpose | |---|---|---| | `--zjit` | off | enable ZJIT at boot | | `--zjit-call-threshold=N` | 30 | calls before a method is compiled | | `--zjit-num-profiles=N` | 5 | profiled calls gathered before compiling | | `--zjit-mem-size=N` | 128 MiB | maximum memory ZJIT may use | | `--zjit-stats[=quiet]` | off | collect statistics | | `--zjit-disable` | - | pass options now, call `RubyVM::ZJIT.enable` later | `RubyVM::ZJIT.enabled?` reports its state, and `RUBY_DESCRIPTION` shows `+ZJIT` when it is on. The interpreter's source also reads a `RUBY_ZJIT_ENABLE` environment variable, although the 4.0 documentation does not list it. ## One JIT per process The two compilers are mutually exclusive: - `ruby --yjit --zjit app.rb` prints "Only one JIT can be enabled at the same time. Exiting" and stops. - `RubyVM::YJIT.enable` while ZJIT is on (or `RubyVM::ZJIT.enable` while YJIT is on) warns and returns `false`. - `RUBY_YJIT_ENABLE` is read only when no JIT flag was given, so `--zjit` on the command line wins over it. ## What `--jit` means now `ruby --help` describes `--jit` as "Enable the default JIT for the build; same as --yjit". Concretely, it is `--yjit` when Ruby was built with YJIT and `--zjit` when only ZJIT was built. The flag has a history worth knowing because old scripts still pass it: | Ruby | `--jit` meant | |---|---| | 3.0 | MJIT, the C-compiler-based JIT | | 3.1 | renamed MJIT's flag to `--mjit`; `--jit` became an alias of `--yjit` on non-Windows x86-64, of `--mjit` elsewhere | | 3.3 | MJIT removed; RJIT, a pure-Ruby experimental JIT, added with `--rjit` | | 4.0 | `--rjit` removed; `--jit` is the build's default JIT | ## So, production? - On Ruby 4.0, run **YJIT** if you run a JIT at all. - Try **ZJIT** in a benchmark or staging environment to see how your code behaves, and report problems upstream, as its documentation invites. - Do not script `--jit` in production if you care which compiler you get; name `--yjit` explicitly.

  • How does ZJIT's compilation strategy differ from YJIT's?
    YJIT compiles lazily, one basic block at a time, and specialises each block for the types it meets (basic block versioning). ZJIT is method-based: the interpreter first records type profiles for a method (five profiled calls by default), and ZJIT then compiles the whole method using that profile to guide optimisation.
  • A deploy script from Ruby 3.0 days starts the app with --jit; what does that do on Ruby 4.0?
    It enables the build's default JIT, which is YJIT on any build that includes it. On 3.0 the same flag meant MJIT, which was removed in 3.3. The script works, but naming `--yjit` explicitly makes the intended compiler obvious.

saying these in an interview costs you the question

  • ZJIT replaced YJIT as the recommended production JIT in 4.0
  • YJIT and ZJIT can run together in one process
  • --jit still enables MJIT on Ruby 4.0
  • ZJIT is enabled by default because it is new in 4.0
  • --rjit is the supported way to try a Ruby-written JIT in 4.0