Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Benchmark

monoruby is performance-focused, and performance is measured continuously rather than quoted from a README. This page covers the live dashboards, how to reproduce a measurement locally, and the historical one-off comparisons that used to live in the project wiki.

Live dashboards

Every push to master that touches the interpreter re-runs the benchmark suite and republishes the project portal:

DashboardWhat it shows
Performance vs YJIT (x86-64)Per-benchmark speed relative to CRuby + YJIT, plus a history chart per benchmark
Performance vs YJIT (aarch64)The same suite on an Apple Silicon runner

The portal also hosts the two ruby/spec dashboards; those are covered in Compatibility.

Methodology, from .github/workflows/bench.yml:

  • The suite is yjit-bench, run with --rss --harness=harness-warmup, monoruby against ruby --yjit on the same runner in the same job.
  • The reference CRuby is 4.0.2 (ruby/setup-ruby), and monoruby is installed with cargo install --path monoruby --locked.
  • Each benchmark gets a 400-second timeout per interpreter; a benchmark that times out or exits non-zero is recorded as a failure rather than silently dropped, and shows on the dashboard as a gap.
  • The published ratio is × YJIT, higher = monoruby faster, 1× being parity. latest.json and data/history.csv next to each dashboard hold the raw numbers if you want to plot them yourself.

As a snapshot: on the x86-64 run of commit a13bfe0 (2026-08-31), 59 of the 76 benchmarks produced a ratio on both interpreters; over those the geometric mean was 1.28× YJIT, with monoruby ahead on 30 of them. The spread is wide in both directions — from ~16× on string_malloc_pressure down to ~0.3× on send_bmethod — which is the point of reading the dashboard rather than a single headline number.

Reproducing a measurement locally

The helper scripts in bin/ wrap the two harnesses the project uses. Most of them assume benchmark-driver and rbenv-managed reference Rubies; adjust the version strings inside to match what you have installed.

ScriptWhat it runs
bin/benchThe standard set (app_fib, so_nbody, so_mandelbrot, app_aobench, plb2) via benchmark-driver, against 4.0.5 --yjit and 4.0.5 --zjit
bin/compareComparing two git refs of monoruby against each other — bin/compare HEAD~1 HEAD by default, over app_fib, so_nbody, so_mandelbrot, quick_sort, integer, vm_send, vm_block, vm_yield
bin/ruby-benchThe full yjit-bench suite (expects a ../ruby-bench checkout), the same harness CI uses
bin/optcarrotoptcarrot on ruby, ruby --yjit and monoruby in turn (expects ../optcarrot)
bin/opt.rboptcarrot fps history over 3000 frames, the data behind the fps-history charts
bin/indexArray / Hash element access (benchmark/index.yaml)
bin/inlineInteger and Math methods, i.e. the inline-asm builtins, also run with --no-jit for contrast
bin/ivarInstance-variable get/set, generic and attr_-generated
bin/sendMethod dispatch, Class.new, Array literals and constant lookup
bin/timesInteger#times, JIT’ed Array / Hash work, block_given?

bin/compare is the one to reach for when you want to know whether a change you just made helped:

bin/compare                          # HEAD~1 vs HEAD, standard benchmarks
bin/compare abc1234 def5678          # two specific commits
bin/compare HEAD~5 HEAD app_fib.yml  # one benchmark

For a single script, a release build is enough:

cargo build --release
target/release/monoruby benchmark/app_fib.rb

Benchmark scripts and their benchmark-driver YAML configs live in benchmark/. Passing --no-jit gives you the VM-only baseline for the same script, which is often more informative than an absolute number.

Profiling

# Flame graph via Linux perf (needs ../FlameGraph)
bin/perf benchmark/app_fib.rb

# or by hand
cargo build --release --features perf
perf record target/release/monoruby benchmark/app_fib.rb
perf report

The perf feature makes monoruby emit perf-compatible symbol maps so JIT-compiled frames get names instead of raw addresses. .cargo/config.toml sets -Cforce-frame-pointers=yes globally, which is what makes the stacks walkable.

--features profile collects deopt and recompile statistics instead of a time profile — see Development and Build Options. Where optcarrot –opt spends its time is a worked example of both.

Historical measurements

The two comparisons below are one-off measurements previously published in the project wiki. They are kept for the record and are not re-measured; for current numbers use the live dashboards above.

optcarrot (April 2024)

Measured with optcarrot.

Rubies:

  • ruby 3.4.0dev (2024-04-27T08:56:20Z master 9ea77cb351) [x86_64-linux]
  • truffleruby 24.0.1, like ruby 3.2.2, Oracle GraalVM JVM [x86_64-linux]
  • truffleruby 24.0.1, like ruby 3.2.2, Oracle GraalVM Native [x86_64-linux]
  • monoruby 3e348afd4141c40978342e67ad26d42dc0b8d2a7

fps history, 0–3000 frames:

With --opt (optcarrot’s self-rewriting optimization mode):

yjit-bench (December 2024)

Speed ratio against truffleruby; higher is better. Measured with yjit-bench using --rss --harness=harness-warmup. Benchmark sources are from ruby/ruby’s benchmark/ and plb2.

Rubies:

  • monoruby 0.3.0
  • ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +YJIT +PRISM [x86_64-linux]
  • truffleruby 24.1.1, like ruby 3.2.4, Oracle GraalVM Native [x86_64-linux]

Raw data — execution time in milliseconds, resident set size (RSS) in MiB. monoruby/yjit and monoruby/truffle are time ratios; above 1 means monoruby is faster.

benchmonoruby (ms)RSS (MiB)yjit (ms)RSS (MiB)truffle (ms)RSS (MiB)monoruby/yjitmonoruby/truffle
bedcov4412.0234.34803.9413.81881.81909.50.9182.345
binarytrees175.028.7137.022.031.71126.41.2785.525
matmul39.735.0121.822.81.4803.20.32629.059
nbody8.527.721.914.01.1690.20.3897.473
nqueens14.724.731.014.27.2637.90.4752.044
optcarrot520.479.0720.954.7432.21506.30.7221.204
rubykon214.934.9348.218.665.22279.90.6173.298
so_mandelbrot39.922.9509.514.526.3548.80.0781.517
sudoku41.623.988.814.917.51165.20.4692.381
fib16.723.517.415.08.8483.40.9601.895

Machine

Both historical runs used the same machine:

  • Architecture: x86_64
  • CPU(s): 32 — 13th Gen Intel(R) Core(TM) i9-13900HX, 16 cores / 2 threads per core
  • Caches (sum of all): L1d 768 KiB (16), L1i 512 KiB (16), L2 32 MiB (16), L3 36 MiB (1)