skip to content

In Ruby 4.0, what are the ways to turn on YJIT, and how do you prove a running process really has it enabled?

level: middleimportance: must knowfreq 50%

answer

  1. flag, env var, or Ruby call
  2. --yjit on the command line
  3. RUBY_YJIT_ENABLE accepts 1, true, yes
  4. RubyVM::YJIT.enable returns true or false
  5. +YJIT in RUBY_DESCRIPTION

basics

~10 s

Enable YJIT with the --yjit flag, the RUBY_YJIT_ENABLE=1 environment variable, or RubyVM::YJIT.enable from Ruby code. Prove it with RubyVM::YJIT.enabled? returning true or +YJIT appearing in RUBY_DESCRIPTION, the string ruby -v prints.

solid answer

~40 s

There are three switches. `ruby --yjit app.rb` enables it at boot; `RUBY_YJIT_ENABLE=1` does the same when you cannot change the command line (only `1`, `true` or `yes` count, and it is ignored if a JIT flag was already given); and `RubyVM::YJIT.enable`, since Ruby 3.3, turns it on at run time - it returns `true`, or `false` if YJIT was already on or ZJIT is running, and in 4.0 it accepts `mem_size:` and `call_threshold:`. To verify, check `RubyVM::YJIT.enabled?`, or look for `+YJIT` in `RUBY_DESCRIPTION`, which Ruby rewrites when YJIT is enabled at run time. A Ruby built without Rust has no YJIT: `--yjit` only warns and the app runs interpreted, so assert the check in production rather than trusting the flag.

code

bash · 4 lines
bash
ruby --yjit -e 'p RubyVM::YJIT.enabled?'                  # true
RUBY_YJIT_ENABLE=1 ruby -e 'p RUBY_DESCRIPTION.include?("+YJIT")'  # true
RUBY_YJIT_ENABLE=on ruby -e 'p RubyVM::YJIT.enabled?'      # false: only 1, true, yes count
ruby -e 'p RubyVM::YJIT.enable; p RubyVM::YJIT.enable'     # true, then false

go deeper

for a junior

Recall the three switches - --yjit, RUBY_YJIT_ENABLE=1, RubyVM::YJIT.enable - and that RubyVM::YJIT.enabled? confirms it.

for a middle

Explain the strict env-var values, enable's true/false return, the 4.0 keyword arguments, and that --yjit only warns on a build without YJIT.

for a senior

Show you verify at boot and alert, because every failure here is silent: a typo, a Rust-less build or a dropped flag still serves traffic.

for a principal

Decide which switch the platform owns so JIT state is set in one place and visible in logs, rather than scattered across scripts.

## Three switches Ruby 4.0 ships YJIT **disabled by default**. You turn it on in one of three ways: 1. **Command-line flag** - `ruby --yjit app.rb`. Any YJIT tuning option (`--yjit-mem-size=...`, `--yjit-call-threshold=...`) also starts YJIT at boot unless you add `--yjit-disable`. 2. **Environment variable** - `RUBY_YJIT_ENABLE=1`. YJIT's documentation offers it for deployment scripts where adding a command-line option is impractical. 3. **From Ruby** - `RubyVM::YJIT.enable`, available since Ruby 3.3. This lets an application finish loading first, so the code that runs only during boot is not compiled. ## The details that trip people up **The environment variable is strict.** `ruby.c` accepts exactly `"1"`, `"true"` or `"yes"`. `RUBY_YJIT_ENABLE=on` or `=enabled` leaves YJIT off without any warning. The variable is also read only when no JIT flag was given on the command line, so an explicit flag wins over it. **`RubyVM::YJIT.enable` has a return value.** It returns `true` after enabling YJIT. It returns `false` when YJIT is already enabled, and it warns "Only one JIT can be enabled at the same time." and returns `false` when ZJIT is running. In Ruby 4.0 it takes keyword arguments: - `stats:` - `false`, `true` (collect and print at exit) or `:quiet` (collect only); - `log:` - the same three values for the compilation log; - `mem_size:` - an Integer in MiB between 1 and 2048, else `ArgumentError`; - `call_threshold:` - a positive Integer, else `ArgumentError`. `mem_size:` and `call_threshold:` are new in 4.0; on 3.3 and 3.4 you had to pass the matching command-line flags with `--yjit-disable`. **The build may not have YJIT at all.** YJIT is written in Rust, so a Ruby compiled on a machine without `rustc` lacks it. Running such a Ruby with `--yjit` prints "Ruby was built without YJIT support" and carries on interpreting. The `RubyVM::YJIT` module may not even be defined on such a build (its own documentation says so), so guard calls with `defined?(RubyVM::YJIT)`. ## Proving it is on | Check | Where | What it shows | |---|---|---| | `RubyVM::YJIT.enabled?` | inside the process | `true` only while YJIT is enabled | | `RUBY_DESCRIPTION` includes `"+YJIT"` | inside the process, logs, error reports | set at boot, and rewritten by `RubyVM::YJIT.enable` | | `ruby --yjit -v` | shell | the version line gains `+YJIT` | | `RubyVM::YJIT.runtime_stats` | inside the process | `nil` when YJIT is off, a Hash when it is on | Two checks look similar but prove something else: - `defined?(RubyVM::YJIT)` is truthy on any build that includes YJIT, enabled or not. - `RubyVM::YJIT.stats_enabled?` reports whether statistics collection (`--yjit-stats`) is on, not whether YJIT is. ## A habit worth having Log the verification once at boot, for example the value of `RUBY_DESCRIPTION`, and alert when a deployment that is meant to run YJIT reports otherwise. The failure modes are silent: a misspelled environment variable, a Ruby compiled without Rust, or a process manager that drops the flag all leave the app running correctly, just without the JIT. ## Summary - Flag, environment variable or `RubyVM::YJIT.enable` - pick the one your deployment controls. - `RUBY_YJIT_ENABLE` needs `1`, `true` or `yes`. - `enable` returns `true` once and `false` afterwards. - Verify with `enabled?` or `+YJIT`, never by the presence of the flag alone.

  • You set RUBY_YJIT_ENABLE=1 but also start the process with --zjit; which JIT runs?
    ZJIT. Ruby reads `RUBY_YJIT_ENABLE` only when no JIT flag was given on the command line, so the explicit `--zjit` wins and the variable is ignored. Only one JIT can be active in a process; passing both `--yjit` and `--zjit` makes Ruby warn "Only one JIT can be enabled at the same time. Exiting" and stop.
  • How do you pass YJIT tuning options while still enabling it only after boot?
    Start Ruby with the tuning flags plus `--yjit-disable`, for example `--yjit-mem-size=96 --yjit-disable`; any `--yjit-*` option otherwise starts YJIT at boot. Then call `RubyVM::YJIT.enable` once the app is loaded. On Ruby 4.0 you can skip the flags and pass `mem_size:` and `call_threshold:` to `enable` directly.

saying these in an interview costs you the question

  • Setting RUBY_YJIT_ENABLE=on is enough to turn YJIT on
  • If defined?(RubyVM::YJIT) is truthy, YJIT must be running
  • RubyVM::YJIT.stats_enabled? tells you whether YJIT is on
  • Running ruby --yjit on a build without Rust aborts the process
  • RubyVM::YJIT.enable raises an error when YJIT is already on