scripts/bench defaults — workers=1, c=64, 3×20s runs per cell. Quiet machine throughout (Kamal services moved off bench port).
These numbers measure one specific Rails reference app — not arbitrary Rails workloads. See #16 for the fixture-fragility caveats.
Each endpoint is its own chart. Bars are log-scaled; raw req/sec is shown at the right.
Same Ruby interpreter, same YJIT, same Puma — the only variable is whether the framework runtime is Rails or Roundhouse-emitted. The multiplier above each pair is the lift from the lowerer pipeline.
YJIT consistently helps Roundhouse-emitted Ruby (small, predictable call shapes). On Rails it's neutral-to-slightly-negative — the deep autoload + reflection chain blunts it.
Throughput normalized by memory footprint. Reorders the table — rust and crystal pull further ahead; high-RSS targets (typescript, rails) move down. Applicability varies by deployment shape: matters most for metered/serverless surfaces, least for bare-metal-with-headroom.
Per-target lift going from /articles (HTML) to /articles.json (JSON). Compiled targets gain the most because the JSON path skips the view-render pipeline (string concat, HTML escape, layout wrap). The Go gap motivates the IR string-builder annotation work in #18.
Max RSS observed across all endpoints, per target. Three orders of magnitude separate rust from rails on the same workload.
Latencies are c=64 concurrent connections; treat p50 as median per-connection wait, p99 as tail. For the AOT/JIT targets these are sub-millisecond on the no-DB endpoint.
| target | /articles | /articles/1 | /articles/new | /articles.json | /articles/1.json | |||||
|---|---|---|---|---|---|---|---|---|---|---|
| p50 | p99 | p50 | p99 | p50 | p99 | p50 | p99 | p50 | p99 | |
| rust | 1.7 | 3.2 | 1.2 | 2.2 | 0.6 | 1.6 | 0.5 | 1.6 | 0.5 | 1.3 |
| crystal | 4.4 | 5.1 | 3.6 | 4.5 | 1.6 | 1.9 | 1.7 | 2.2 | 1.3 | 1.7 |
| go | 19.1 | 24.1 | 17.2 | 21.2 | 3.5 | 37.4 | 4.6 | 7.2 | 4.5 | 7.0 |
| typescript | 12.0 | 24.1 | 9.1 | 15.2 | 3.4 | 4.2 | 5.7 | 11.9 | 4.8 | 13.1 |
| ruby | 16.6 | 18.7 | 14.8 | 23.0 | 9.5 | 11.7 | 11.1 | 14.1 | 9.5 | 12.0 |
| ruby-int | 21.4 | 23.2 | 17.8 | 21.9 | 11.8 | 14.1 | 13.8 | 16.5 | 12.0 | 14.2 |
| rails | 128.0 | 301.0 | 131.9 | 269.6 | 91.0 | 218.3 | 75.3 | 98.7 | 48.1 | 74.2 |
| rails-int | 125.3 | 312.6 | 128.9 | 212.0 | 89.6 | 223.3 | 70.0 | 87.2 | 46.2 | 54.4 |
All values in milliseconds. Lower is better.
| target | endpoint | req/sec | p50 (ms) | p99 (ms) | RSS (MB) | req/sec/GB |
|---|---|---|---|---|---|---|
| rust | /articles | 37,109 | 1.70 | 3.16 | 17 | 2,202,902 |
| rust | /articles/1 | 53,860 | 1.15 | 2.25 | 17 | 3,128,583 |
| rust | /articles/new | 91,887 | 0.62 | 1.55 | 18 | 5,198,063 |
| rust | /articles.json | 98,161 | 0.53 | 1.64 | 18 | 5,529,102 |
| rust | /articles/1.json | 126,002 | 0.46 | 1.29 | 18 | 7,089,683 |
| crystal | /articles | 14,308 | 4.42 | 5.09 | 23 | 626,412 |
| crystal | /articles/1 | 17,238 | 3.64 | 4.47 | 20 | 843,232 |
| crystal | /articles/new | 39,144 | 1.58 | 1.94 | 19 | 2,055,590 |
| crystal | /articles.json | 34,846 | 1.74 | 2.22 | 22 | 1,552,464 |
| crystal | /articles/1.json | 47,837 | 1.29 | 1.72 | 23 | 2,108,329 |
| go | /articles | 3,344 | 19.10 | 24.07 | 32 | 104,237 |
| go | /articles/1 | 3,713 | 17.23 | 21.25 | 33 | 114,172 |
| go | /articles/new | 10,692 | 3.49 | 37.42 | 35 | 308,971 |
| go | /articles.json | 13,770 | 4.58 | 7.18 | 33 | 415,116 |
| go | /articles/1.json | 14,175 | 4.49 | 7.02 | 33 | 428,220 |
| typescript | /articles | 5,072 | 11.96 | 24.11 | 394 | 13,157 |
| typescript | /articles/1 | 6,771 | 9.06 | 15.18 | 394 | 17,570 |
| typescript | /articles/new | 18,352 | 3.43 | 4.18 | 392 | 47,845 |
| typescript | /articles.json | 10,665 | 5.71 | 11.94 | 392 | 27,793 |
| typescript | /articles/1.json | 12,366 | 4.84 | 13.12 | 393 | 32,195 |
| ruby | /articles | 3,835 | 16.65 | 18.68 | 102 | 38,179 |
| ruby | /articles/1 | 4,272 | 14.75 | 22.98 | 128 | 33,971 |
| ruby | /articles/new | 6,658 | 9.50 | 11.73 | 139 | 48,756 |
| ruby | /articles.json | 5,659 | 11.12 | 14.09 | 143 | 40,430 |
| ruby | /articles/1.json | 6,692 | 9.46 | 11.95 | 146 | 46,887 |
| ruby-int | /articles | 2,985 | 21.44 | 23.24 | 87 | 34,978 |
| ruby-int | /articles/1 | 3,578 | 17.77 | 21.93 | 115 | 31,853 |
| ruby-int | /articles/new | 5,381 | 11.81 | 14.09 | 124 | 44,183 |
| ruby-int | /articles.json | 4,609 | 13.80 | 16.46 | 127 | 36,948 |
| ruby-int | /articles/1.json | 5,318 | 12.02 | 14.20 | 133 | 40,863 |
| rails | /articles | 477 | 128.00 | 301.00 | 318 | 1,535 |
| rails | /articles/1 | 468 | 131.93 | 269.63 | 332 | 1,441 |
| rails | /articles/new | 682 | 91.04 | 218.32 | 340 | 2,052 |
| rails | /articles.json | 857 | 75.34 | 98.67 | 348 | 2,521 |
| rails | /articles/1.json | 1,265 | 48.09 | 74.24 | 397 | 3,256 |
| rails-int | /articles | 489 | 125.32 | 312.62 | 306 | 1,633 |
| rails-int | /articles/1 | 476 | 128.91 | 212.03 | 314 | 1,549 |
| rails-int | /articles/new | 693 | 89.60 | 223.30 | 328 | 2,159 |
| rails-int | /articles.json | 909 | 70.04 | 87.18 | 339 | 2,748 |
| rails-int | /articles/1.json | 1,314 | 46.23 | 54.41 | 386 | 3,479 |