Zipp benchmarks
This is the canonical home for Zipp's performance numbers. Every table below comes from a checked-in JSON capture with engine hashes, competitor hashes, per-repetition schedules and bootstrap replicates, so anyone can re-run it and anyone can cite it. Ratios are Zipp divided by the competitor: lower is faster.
Headline
- Zipp/Node, all 30 rows, geometric mean
- 0.652×
- Zipp/Bun, all 30 rows
- 0.496×
- Zipp/Deno, all 30 rows
- 0.434×
- Rows where Zipp's median beats Node's
- 23 / 30
normal 13 0.545×, hostile 17 0.748×
normal 0.458×, hostile 0.527×
normal 0.473×, hostile 0.407×
7 rows are slower; they are listed below
Capture: 21 October 2026, the 0.0.28 engine as a clean profile-guided-optimization build recorded as zipp 0.0.28 from source 84012a2d (which differs from the release tag da0fe6b1 only in the website). Native Windows x86-64, four engines, 15 repetitions per row, exact output on every observation, and both artifacts record publishable:true. Every engine runs with its production JIT; Interpreter versus interpreter compares Zipp's interpreter with QuickJS and the JIT-off modes, and Python versus CPython its Python frontend. The all-30 figures weight the 30 rows equally, exp((13 ln G13 + 17 ln G17) / 30) of the two suites' geometric means, whose intervals are given with each suite.
Methodology
- Cold process time. Every observation is a fresh process timed with a wall clock around spawn and exit. A paired empty launch of the same engine runs immediately before each full launch; the headline uses the cold time, and the launch-adjusted time is reported as a diagnostic.
- No warm-up and no
--jitlessgames. Every engine runs with its production JIT and an empty environment; any inherited runtime control such asNODE_OPTIONSorZIPP_NOJITmarks a run diagnostic-only. - Counterbalanced schedule. A seeded engine order rotates once per repetition so each engine occupies every position; the per-repetition order is recorded in the artifact.
- Intervals. 95% percentile bootstrap with 10,000 replicates, described in the artifact as descriptive, not a hypothesis test. Suite geometric means use a cluster bootstrap by repetition. "Win" below means a Bonferroni-adjusted exact one-sided paired sign test at 0.05 across all comparisons (39 for the normal suite, 51 for hostile).
- Correctness first. Standard output is compared byte for byte against the reference on every repetition. A single mismatch disqualifies the capture.
- Publication gate. Full Node, Bun, Deno, Zipp order with Node as baseline; the complete frozen suite; at least 15 repetitions and 10,000 bootstrap samples; a clean,
HEAD-matching, provenance-stamped PGO binary.
| Item | Value |
|---|---|
| CPU | AMD Ryzen 9 9950X3D (AMD64 Family 26 Model 68), High performance power plan. RAM is not recorded. |
| OS | Windows 11, build 10.0.26200 |
| Zipp build | rustc 1.92.0 (LLVM 21.1.3), x86_64-pc-windows-msvc, --no-default-features release: opt-level=3, lto=fat, codegen-units=1, panic=abort, -Ctarget-cpu=x86-64 (not native), -Cprofile-use PGO, rust-lld |
| Zipp binary | zipp 0.0.28, SHA-256 dedff1a3…32aa83, source 84012a2d, not dirty |
| Node | v24.19.0, SHA-256 3602f2bb… |
| Bun | 1.3.14 via bun run, SHA-256 0187f68d… |
| Deno | 2.6.10 stable via deno run, SHA-256 49e2f75a… |
| Harness | tools/bench.py and tools/bench_hostile.py, Python 3.11.6, seed 0x5A172026, 300-second timeout |
Startup
Median empty-process launch, the paired baseline for every row:
| Engine | Launch |
|---|---|
| Zipp | 7.5 |
| Node | 30.7 |
| Bun | 50.9 |
| Deno | 79.3 |
Normal suite: 13 rows
Ten headline workloads plus three architecture diagnostics that are deliberately kept out of the headline geometric mean. Cold medians in milliseconds; the interval is the 95% bootstrap on the Zipp/Node ratio; an asterisk marks a Bonferroni-adjusted sign-test win over Node.
| Row | Node | Bun | Deno | Zipp | Zipp/Node | Zipp/Bun | Zipp/Deno |
|---|---|---|---|---|---|---|---|
| async-promise-chain | 337.8 | 375.8 | 353.3 | 258.1 | 0.768* [0.755, 0.780] | 0.684 | 0.737 |
| class-prototype-hot | 301.3 | 338.6 | 322.2 | 233.6 | 0.778* [0.771, 0.791] | 0.691 | 0.737 |
| json-large | 261.9 | 199.7 | 316.4 | 216.4 | 0.836* [0.791, 0.854] | 1.100 | 0.695 |
| map-set-heavy | 648.1 | 757.1 | 1138.5 | 461.7 | 0.710* [0.688, 0.772] | 0.610 | 0.413 |
| markdown-render | 267.6 | 214.5 | 302.3 | 183.0 | 0.680* [0.666, 0.693] | 0.829 | 0.615 |
| parse-large-js | 269.9 | 236.1 | 282.1 | 232.5 | 0.867* [0.849, 0.871] | 0.993 | 0.832 |
| polymorphic-objects | 327.6 | 337.5 | 335.3 | 210.7 | 0.644* [0.640, 0.656] | 0.626 | 0.632 |
| polymorphic-objects-v2 | 81.2 | 94.7 | 127.4 | 22.2 | 0.271* [0.263, 0.287] | 0.231 | 0.172 |
| property-ic-shapes | 258.0 | 165.5 | 305.5 | 9.3 | 0.037* [0.035, 0.037] | 0.058 | 0.030 |
| regex-log-scan | 479.5 | 561.8 | 457.4 | 389.9 | 0.826* [0.790, 0.847] | 0.701 | 0.866 |
| sparse-array | 79.8 | 106.9 | 123.8 | 71.3 | 0.901* [0.889, 0.914] | 0.675 | 0.586 |
| sparse-array-v2 | 171.4 | 369.8 | 181.4 | 93.6 | 0.547* [0.534, 0.553] | 0.252 | 0.523 |
| typedarray-math | 200.3 | 916.4 | 163.7 | 139.3 | 0.693* [0.674, 0.700] | 0.151 | 0.846 |
| Set | Geometric mean | 95% interval |
|---|---|---|
| Headline 10 | 0.766× | [0.758, 0.771] |
| Diagnostics 3 | 0.176× | [0.172, 0.180] |
| All 13 | 0.545× | [0.540, 0.549] |
Note what parse-large-js measures: a hand-written userland tokenizer over two megabytes of synthetic source. It does not benchmark Zipp's own parser. The diagnostic rows isolate the inline-cache cliff at nine receiver shapes and the sparse-array gap-size axes; they are useful for engineering and would flatter the headline, which is why they are excluded from it.
Hostile suite: 17 rows
The hostile corpus asks how gracefully Zipp performs when JavaScript stops looking like an ideal reducer: six baseline-and-stressor pairs plus five standalone applications, including a React-style reconciler, a warm HTTP router, a bytecode interpreter and the unmodified [email protected] package.
| Row | Zipp/Node | Row | Zipp/Node |
|---|---|---|---|
| calls-baseline | 0.456 | calls-closures | 1.123 |
| shapes-stable | 1.043 | shapes-megamorphic | 1.104 |
| types-stable | 0.499 | types-churn | 0.693 |
| branch-control | 0.839 | throw-catch | 0.272 |
| allocation-ephemeral | 0.357 | allocation-survival | 1.377 |
| async-burst | 0.574 | async-lived | 0.829 |
| reactish-reconcile | 1.257 | warm-router | 1.253 |
| bytecode-vm | 1.050 | module-hot-graph | 0.403 |
| npm-nanoid | 0.992 |
Ordinary geometric mean 0.748× Node [0.740, 0.757], 0.527× Bun, 0.407× Deno; category-balanced 0.784× Node. The suite's aspirational gate (category-balanced at most 1.05×, no category above 1.15×, no row above 1.50×) passes its category-balanced bound, fails the per-category bound (applications 1.257×, server 1.253×), and passes the row bound (the worst row, allocation-survival, is 1.377×).
Where Zipp loses
7 of thirty rows have a Node point gap, all of them in the hostile suite: allocation-survival 1.377×, reactish-reconcile 1.257×, warm-router 1.253×, calls-closures 1.123×, shapes-megamorphic 1.104×, bytecode-vm 1.050×, shapes-stable 1.043×. The pattern: long-lived allocation, property access across many or changing shapes, closure-heavy application code and a large switch dispatcher are where V8's optimizing tiers still win. Against the previous capture (18 October 2026, zipp 0.0.26), all 30 rows went from 0.634× to 0.652× Node; the row that improved most was throw-catch (0.368× to 0.272×), and the ones that moved most the other way were async-lived (0.723× to 0.829×) and allocation-ephemeral (0.314× to 0.357×). Zipp's own medians are 1.2% lower than in that capture, but Node's, the same v24.19.0 binary, are 4.4% lower (7.5% on the short hostile rows), so most of the ratios' movement is in the Node runs, not in Zipp.
Against Bun, Zipp loses json-large (1.100×). Against Deno, Zipp loses no row. On the normal suite Bun is the absolute fastest engine on json-large; Zipp is fastest on every other normal row.
Interpreter versus interpreter
The tables above compare production JITs. This one asks how fast Zipp is as a pure interpreter: the same PGO binary with ZIPP_NOJIT=1 ZIPP_NO_RX_JIT=1, which emits no native code (its JavaScript JIT and its regex JIT both off; the captures before 22 October left the regex JIT on), against Bellard's QuickJS 2026-06-04 and QuickJS-NG 0.17.0 (neither has a JIT), and against Node --jitless (V8's Ignition), Deno --v8-flags=--jitless and Bun with BUN_JSC_useJIT=0 (JavaScriptCore's LLInt). run_native.py staged identical sources for every engine, checked every output against Node's and ran 8 counterbalanced repetitions; cold medians in milliseconds, launch included. It is a diagnostic, not a canonical capture. This interpreter is what the WebAssembly build and the safe-sandbox profile run.
| Engine | Normal 13 | Hostile 17 | All 30 | Zipp faster |
|---|---|---|---|---|
| QuickJS | 0.615× [0.600, 0.621] | 0.740× [0.733, 0.747] | 0.683× | 25 / 30 |
| QuickJS-NG | 0.390× [0.385, 0.396] | 0.387× [0.384, 0.391] | 0.388× | 30 / 30 |
| Node jitless | 1.224× [1.213, 1.241] | 1.022× [1.015, 1.029] | 1.105× | 9 / 30 |
| Deno jitless | 1.168× [1.155, 1.185] | 0.880× [0.863, 0.887] | 0.995× | 15 / 30 |
| Bun no-JIT | 0.829× [0.812, 0.840] | 0.785× [0.773, 0.794] | 0.804× | 24 / 30 |
| Row | Zipp interpreter | QuickJS | QuickJS-NG | Node jitless | Deno jitless | Bun no-JIT | Zipp/QuickJS |
|---|---|---|---|---|---|---|---|
| async-promise-chain | 398.6 | 3,925 | 3,862 | 456.6 | 453.4 | 571.0 | 0.102 |
| class-prototype-hot | 3,692 | 3,844 | 7,243 | 1,737 | 1,726 | 4,939 | 0.961 |
| json-large | 541.0 | 805.1 | 1,458 | 545.6 | 566.0 | 662.5 | 0.672 |
| map-set-heavy | 668.7 | 1,574 | 2,118 | 751.2 | 1,147 | 1,147 | 0.425 |
| markdown-render | 686.6 | 1,095 | 2,100 | 775.5 | 814.7 | 1,012 | 0.627 |
| parse-large-js | 1,736 | 2,038 | 3,668 | 1,371 | 1,380 | 1,609 | 0.852 |
| polymorphic-objects | 852.5 | 883.2 | 1,439 | 602.5 | 580.1 | 1,370 | 0.965 |
| regex-log-scan | 1,708 | 2,566 | 4,068 | 1,560 | 1,518 | 2,800 | 0.665 |
| sparse-array | 290.0 | 386.9 | 808.2 | 207.9 | 219.8 | 278.4 | 0.750 |
| typedarray-math | 4,238 | 3,808 | 6,522 | 2,239 | 2,368 | 3,240 | 1.113 |
| polymorphic-objects-v2 | 390.1 | 398.1 | 855.6 | 298.6 | 319.1 | 425.8 | 0.980 |
| property-ic-shapes | 2,678 | 2,036 | 5,261 | 1,921 | 1,969 | 1,817 | 1.315 |
| sparse-array-v2 | 327.1 | 1,855 | 876.8 | 322.6 | 314.2 | 475.7 | 0.176 |
| Row | Zipp interpreter | QuickJS | QuickJS-NG | Node jitless | Deno jitless | Bun no-JIT | Zipp/QuickJS |
|---|---|---|---|---|---|---|---|
| calls-baseline | 99.0 | 110.7 | 251.9 | 103.0 | 117.4 | 106.8 | 0.894 |
| calls-closures | 231.8 | 233.6 | 453.3 | 175.7 | 195.1 | 246.3 | 0.992 |
| shapes-stable | 87.2 | 131.2 | 261.1 | 86.6 | 104.0 | 89.5 | 0.665 |
| shapes-megamorphic | 156.3 | 190.1 | 328.7 | 111.8 | 131.1 | 185.9 | 0.823 |
| types-stable | 158.4 | 163.0 | 374.4 | 126.1 | 145.9 | 129.8 | 0.971 |
| types-churn | 105.3 | 97.4 | 202.0 | 98.0 | 110.9 | 114.1 | 1.081 |
| branch-control | 103.2 | 109.4 | 236.2 | 97.5 | 116.3 | 102.9 | 0.943 |
| throw-catch | 99.9 | 97.9 | 295.3 | 160.9 | 139.2 | 150.9 | 1.020 |
| allocation-ephemeral | 112.1 | 253.2 | 365.4 | 120.6 | 135.3 | 259.6 | 0.443 |
| allocation-survival | 106.1 | 174.8 | 264.1 | 101.6 | 122.2 | 131.1 | 0.607 |
| async-burst | 33.1 | 101.9 | 125.3 | 60.0 | 78.6 | 43.5 | 0.324 |
| async-lived | 33.8 | 107.8 | 126.4 | 49.4 | 69.7 | 68.0 | 0.314 |
| reactish-reconcile | 82.4 | 123.5 | 178.3 | 72.1 | 89.9 | 106.4 | 0.667 |
| warm-router | 119.1 | 123.2 | 474.1 | 76.5 | 102.0 | 140.5 | 0.967 |
| bytecode-vm | 332.9 | 404.6 | 943.6 | 298.5 | 310.1 | 484.0 | 0.823 |
| module-hot-graph | 113.8 | 99.4 | 194.7 | 102.7 | 121.5 | 143.1 | 1.145 |
| npm-nanoid | 514.8 | 697.8 | 1,159 | 456.3 | 469.4 | 800.6 | 0.738 |
Over all 30 rows Zipp's interpreter, with no native code (ZIPP_NOJIT=1 ZIPP_NO_RX_JIT=1), is faster than Bellard's QuickJS (0.683×, 25 of 30 rows; 0.685× on 21 October, when its regex JIT was left on), and faster than QuickJS-NG on all of them (0.388×). QuickJS wins property-ic-shapes (1.315×), module-hot-graph (1.145×), typedarray-math (1.113×), types-churn (1.081×), throw-catch (1.020×). Against the JIT engines with their JITs off it is slower than Node --jitless (1.105×, 9 of 30 rows), faster than Deno --jitless (0.995×, 15 of 30 rows), and faster than Bun without its JIT (0.804×, 24 of 30 rows).
Python versus CPython
Zipp compiles Python 3 to the same register bytecode. tools/python_bench.py runs 36 programs (30 micro kernels, 6 application-shaped) as a fresh process per engine per repetition, interleaved, 15 repetitions, against CPython 3.13.14; every run's output must equal CPython's. The binary is the published 0.0.28 release build (the PGO build has no Python). Each program times its own workload in-process; that work time is the ratio below. Zipp runs with its JIT and again with ZIPP_NOJIT=1, since standard CPython is an interpreter. 8 further programs (the torch ones) have no CPython counterpart and are not included.
| Set | Zipp with JIT | Zipp, no JIT |
|---|---|---|
| Micro 30 | 0.975× | 1.400× |
| Macro 6 | 1.674× | 1.680× |
| All 36 | 1.067× | 1.443× |
| All, whole-process wall time | 0.539× | 0.576× |
| Program | Set | Zipp | Zipp no JIT | CPython | Zipp/CPython | No JIT/CPython |
|---|---|---|---|---|---|---|
| micro/attr_read_write | micro | 3.33 | 10.86 | 7.87 | 0.423 | 1.375 |
| micro/builtin_isinstance | micro | 9.83 | 14.99 | 9.19 | 1.070 | 1.638 |
| micro/builtin_len | micro | 8.30 | 13.77 | 11.51 | 0.721 | 1.201 |
| micro/call_defaults | micro | 16.36 | 15.71 | 12.50 | 1.309 | 1.266 |
| micro/call_keywords | micro | 9.25 | 8.97 | 7.96 | 1.163 | 1.128 |
| micro/call_simple | micro | 39.05 | 37.80 | 36.05 | 1.083 | 1.039 |
| micro/dict_get | micro | 6.17 | 11.71 | 11.22 | 0.550 | 1.042 |
| micro/dict_str_key | micro | 27.68 | 35.92 | 20.56 | 1.346 | 1.772 |
| micro/float_arith | micro | 2.32 | 11.69 | 9.43 | 0.246 | 1.244 |
| micro/for_list | micro | 15.74 | 23.25 | 16.66 | 0.945 | 1.400 |
| micro/fstring | micro | 7.63 | 9.38 | 3.50 | 2.183 | 2.693 |
| micro/generator_iter | micro | 43.95 | 42.53 | 14.45 | 3.042 | 2.933 |
| micro/global_read | micro | 22.12 | 31.56 | 50.22 | 0.440 | 0.625 |
| micro/hasattr_miss | micro | 2.89 | 5.71 | 2.77 | 1.045 | 2.058 |
| micro/hash_str | micro | 6.92 | 9.18 | 7.89 | 0.877 | 1.154 |
| micro/instance_new | micro | 14.68 | 16.83 | 5.72 | 2.565 | 2.938 |
| micro/int_arith | micro | 64.30 | 77.65 | 146.68 | 0.438 | 0.533 |
| micro/list_append | micro | 4.79 | 11.63 | 5.14 | 0.932 | 2.267 |
| micro/list_index | micro | 17.67 | 28.94 | 26.35 | 0.670 | 1.097 |
| micro/listcomp_small | micro | 13.52 | 15.45 | 7.39 | 1.830 | 2.059 |
| micro/math_sqrt | micro | 10.81 | 15.88 | 9.80 | 1.102 | 1.612 |
| micro/method_call | micro | 14.10 | 14.03 | 8.50 | 1.659 | 1.656 |
| micro/property_read | micro | 12.20 | 12.69 | 8.87 | 1.376 | 1.429 |
| micro/range_loop | micro | 19.93 | 43.59 | 99.25 | 0.201 | 0.437 |
| micro/str_long_len_index | micro | 5.22 | 7.18 | 6.88 | 0.759 | 1.045 |
| micro/str_methods | micro | 22.95 | 24.74 | 7.22 | 3.178 | 3.472 |
| micro/super_call | micro | 15.22 | 13.95 | 8.58 | 1.774 | 1.645 |
| micro/try_raise_catch | micro | 25.70 | 28.19 | 9.06 | 2.838 | 3.124 |
| micro/tuple_swap | micro | 0.77 | 1.52 | 2.93 | 0.262 | 0.520 |
| micro/tuple_unpack | micro | 8.33 | 11.25 | 9.09 | 0.916 | 1.229 |
| macro/fib_recursive | macro | 37.47 | 36.20 | 22.05 | 1.700 | 1.642 |
| macro/json_roundtrip | macro | 6.97 | 6.97 | 7.84 | 0.889 | 0.889 |
| macro/nbody | macro | 14.39 | 17.80 | 9.26 | 1.554 | 1.896 |
| macro/oo_simulation | macro | 25.16 | 24.17 | 12.61 | 1.996 | 1.920 |
| macro/sort_heapq | macro | 12.46 | 11.76 | 5.98 | 2.085 | 1.995 |
| macro/word_count | macro | 22.35 | 21.14 | 9.93 | 2.251 | 2.119 |
With its JIT, Zipp's Python is faster than CPython on 15 of 36 programs; over the micro kernels it takes 0.975× CPython's time, over the application-shaped programs 1.674×, and over all 36 programs 1.067× (1.057× in the previous capture); without the JIT Zipp is 1.443×. Counting the whole process, Zipp's faster start makes it 0.539× CPython's wall time. No intervals are computed for these geometric means; the JIT and no-JIT artifacts hold every observation.
WebAssembly and GPU
None of the numbers above apply to the WebAssembly build, which is an interpreter. The only WebAssembly comparison in the repository, against QuickJS-NG v0.16.2, is marked not publishable: only five hostile rows were comparable, QuickJS-NG led four of them, and Zipp led warm-router at 0.476×. Module size is a settled fact rather than a benchmark: 5,558,860 bytes raw and 1,248,649 Brotli for Zipp's v0.0.12 artifact against 1,528,293 and 417,087 for QuickJS-NG, 3.59× raw and 2.96× on the wire.
Five WebAssembly interpreter cliffs were closed on 5 September 2026 with paired before-and-after measurements on one machine: a 64K non-ASCII charCodeAt loop 4,462 → 3.2 ms, a tokenizer 9,828 → 8.2 ms, a slice loop 449 → 4.3 ms, join with 300K retained strings 8,423 → 138 ms, and a new Function property loop 20.9 → 14.8 ms.
The Python frontend's measurements are in Python versus CPython above. There are no GPU performance numbers at all; the GPU work is validated for correctness only.
The 11 September capture, and why it is slower
An earlier publishable capture exists at commit 14770703 (zipp 0.0.15, 11 September 2026): headline ten 1.003× Node [0.995, 1.009], all 13 rows 0.677×, hostile ordinary 0.875×, with bytecode-vm at 3.59× Node. A paired A/B against the audited baseline e6e0f65d puts that baseline within 1% of the new build on every row, so the regression opened between 8229b3fc and e6e0f65d. The suspect is the v0.0.15 change that made specification-order method calls the default: the fused CallMethod lowering was observably wrong for a getter or a proxy trap and is now used only where argument shapes provably cannot observe the order. No headline is claimed for 14770703; the trade was correctness for speed, and the ledger says so.
- 1.90× Node, headline ten
First attributable default capture; interpreter-only smoke at 6.42×.
- PGO adopted at −13.3%
1.749× to 1.451× in one build change; two more waves reach 1.330× the same day.
- Nursery on by default: 1.212×
- First capture under 1.20×
typedarray-math reaches Node parity.
- 0.9695×: Node parity crossed
Wave 19 decomposed the three rows that were 60% of the remaining gap.
- Hostile suite under parity at 0.961×
- A 2.16× regression, reversed
A de-fused call lowering; root-caused and reverted the same day.
- 0.878× headline, 0.728× all-30: the public capture
- 1.003× headline after strict call order
Correctness kept; speed to be recovered.
- 0.795× headline, 0.703× all-30: the previous capture
All 13 normal rows faster than Node; seven hostile gaps remain.
- 0.769× headline, 0.634× all-30: the 0.0.26 capture
All 13 normal rows faster than Node; 6 hostile gaps remain, and the bytecode VM is no longer one.
- 0.766× headline, 0.652× all-30: the 0.0.28 capture
All 13 normal rows and 10 of 17 hostile rows faster than Node; the interpreter at 0.685× Bellard's QuickJS (0.683× rerun with its regex JIT off too).
A release measurement for v0.0.28
The published binaries are ordinary release builds, not profile-guided ones, so each release is also measured as downloaded. These are descriptive release checks, separate from the canonical capture above. For v0.0.28 (the published zipp-0.0.28-windows-x86_64-portable.exe, source da0fe6b1, 21 October 2026; Node 24.19.0, Bun 1.3.14 and Deno 2.6.10, 15 counterbalanced repetitions, paired medians with bootstrap intervals, exact output on every row):
| Row | Node 24 | Zipp | Zipp / Node |
|---|---|---|---|
async-promise-chain | 328 ms | 299 ms | 0.91× |
class-prototype-hot | 301 ms | 232 ms | 0.77× |
json-large | 258 ms | 247 ms | 0.96× |
map-set-heavy | 617 ms | 510 ms | 0.82× |
markdown-render | 272 ms | 217 ms | 0.80× |
parse-large-js | 268 ms | 244 ms | 0.90× |
polymorphic-objects | 327 ms | 299 ms | 0.91× |
polymorphic-objects-v2 | 81 ms | 26 ms | 0.32× |
property-ic-shapes | 258 ms | 10 ms | 0.04× |
regex-log-scan | 469 ms | 431 ms | 0.91× |
sparse-array | 79 ms | 71 ms | 0.91× |
sparse-array-v2 | 171 ms | 99 ms | 0.58× |
typedarray-math | 199 ms | 139 ms | 0.70× |
The geometric mean is 0.856× Node over the ten headline rows [0.850, 0.869] and 0.608× over all 13 [0.605, 0.617]; Zipp's median is below Node's on every row. A paired A/B of the same 13 rows puts the profile-guided build at 0.891× the release build's time [0.881, 0.901]: the download is about 12% slower than the canonical figures. The release and A/B artifacts hold the observations.
Reproduce it
& 'C:\Program Files\Git\bin\bash.exe' tools/pgo.sh # profile-guided build of target/release/zipp.exe
New-Item -ItemType Directory -Force target\bench-results | Out-Null
python tools\bench.py --zipp target\release\zipp.exe --engines node,bun,deno,zipp `
--reps 15 --bootstrap-samples 10000 --json target\bench-results\real13.json
python tools\bench_hostile.py --zipp target\release\zipp.exe `
--reps 15 --bootstrap-samples 10000 --json target\bench-results\hostile17.json
python -m unittest tools.test_bench- bench/real13_84012a2d_pgo_2026-10-21.json, the normal-suite capture.
- bench/hostile/head_clean_84012a2d_pgo_2026-10-21.json, the hostile capture.
- bench/comparison/interp_real13_84012a2d_2026-10-22.json and its hostile pair, interpreter against interpreter (Zipp with no native code,
ZIPP_NOJIT=1 ZIPP_NO_RX_JIT=1, against QuickJS, QuickJS-NG and the JIT-off modes of Node, Deno and Bun). - bench/python/cpython_da0fe6b1_2026-10-21.json and the no-JIT run, Python against CPython 3.13.14 (the second run sets
ZIPP_NOJIT=1for the whole harness). - bench/comparison/release_real13_da0fe6b1_2026-10-21.json and the PGO A/B, the release build as downloaded.
- bench/real13_f1739644_pgo_2026-10-18.json and its hostile pair, the previous canonical capture (18 October 2026,
zipp 0.0.26), with its interpreter pair and Python run. - bench/real13_fa8e3b86_pgo_2026-10-15.json and its hostile pair, the capture before it (15 October 2026,
zipp 0.0.24). - bench/real13_8229b3fc_pgo_2026-09-02.json and the hostile pair, the previous canonical capture (since 17 October 2026 a row of bench/history; these links are to the last commit that held the files).
- bench/real13_14770703_pgo_2026-09-11.json and the hostile pair, the later capture.
- bench/README.md, the suite map and publication gate; bench/hostile/README.md and manifest.json.
- docs/PERF_ROADMAP.md, the checkable roadmap, and the archived performance ledger, 881 KB of experiments including the refuted ones.