Benchmarks.

Two causl engines, measured against Jotai, Redux Toolkit and MobX by causl-bench: causl-ts is @causlts/core in TypeScript, and causl-wasm is the @causl/causl-wasm-ts client on the Rust causl-core-rs engine. Each cell is one (library × scenario × scale), timed on a world built fresh for the step, in its own process. The charts show median and p95 run by run; the section below reports the latest sweep in full, cell by cell.

Every causl cell carries a runtime attestation of the engine that executed the graph that was timed, so the engine label is an observation rather than a configuration flag. That distinction is not academic. This page once served three wasm-labelled series, two of which were the same TypeScript engine under a wasm label, published for months before anyone noticed. Those two series are now deleted from the feed rather than left on the charts under a correction, and the one attested series carries the name: there is exactly one causl-wasm line here, and every point on it is Rust.

Live history

Per-cell median + p95 + verdict.

The page first tries ./history.json; if the nightly publish hasn't landed yet it falls back to ./history.sample.json, a checked-in multi-run stub so the dashboard is never empty. The “source” cell in the meta strip below tells you which one is showing.

Profile artifacts pending CI restoration

Each cell carries a profile link that points at ./profiles/latest/<library-scenario-scale>/. Those directories are populated by the nightly perf-snapshot workflow, which is currently parked in .github/workflows-disabled/perf-snapshot.yml until GitHub Actions is re-enabled. The links remain useful as discoverable placeholders so the dashboard surface doesn't change shape when CI is restored.

The boundaryCrossings field, and why it says nothing

A handful of the 2026-05 entries in history.json carry a boundaryCrossings integer meant to count JS↔WASM bridge crossings inside the timed region. After the withdrawn wasm series were deleted, the only rows left carrying it are causl-ts rows, where it is 0 because there is no bridge. So the field survives as a residue of a retired harness and measures nothing on the series anyone would want it for.

It is also not the interesting quantity. A raw boundary crossing costs about 1.6 ns on this host, so the ~2,000 crossings a linear-chain@1000 iteration makes come to roughly 0.13% of that cell. The current sweeps do not publish the field; the cells built to probe it are inapplicable on every runner.

Charting library: vanilla SVG (decision log)

D3 (~70 KB minified) and Plotly (~3.5 MB minified) were both evaluated as upgrades over the inline-SVG MVP. We stayed on vanilla SVG and brought the visible upgrades directly into dashboard.js: cubic-Hermite (Catmull-Rom α=0.5) line smoothing, D3-style "nice" axis ticks at 1/2/5 × 10k, and per-point hover tooltips covering every run.

The trade we declined: a lazy-loaded D3 bundle would have added ~70 KB of parse/compile cost to a page whose only interactive surface is a small per-cell sparkline grid. Lighthouse Performance budgets penalise any third-party JS that touches the main thread post-paint, and Plotly is two orders of magnitude over budget. The visible upgrades the issue wanted were ~80 lines of inline math each, not a framework dependency. The dashboard ships with zero external runtime dependencies; the <noscript> block below points no-JS readers at the raw history JSON.

Two withdrawn series were removed from this feed, not corrected in place.

Until now this page served three wasm-labelled series. Two of them, causl-wasm and causl-wasm-all, were produced by the TypeScript engine running under a wasm label: their engine tag was asserted by configuration, never observed. Their generator was deleted upstream in causl-bench#110, which stopped new numbers without retracting the ones this feed was still serving.

So I deleted their 219 samples and 85 skip rows from history.json outright. I did not relabel them: relabelling would attach a Rust-engine claim to a TypeScript-engine measurement, which is the original defect wearing a new name. Losing those historical points is the intended cost. What remains is one causl-wasm series, every cell of it attested at runtime to the Rust engine that ran it: the latest sweep, in full →

Re-measured: 2026-08-21

Every cell, and which engine actually ran it.

One archived causl-bench sweep, reported in full: 395 measured cells and 55 that produced no number, across 5 runners. Each causl cell carries a runtime attestation of the engine that executed the graph that was timed, so the engine label is an observation rather than a configuration flag. Asserting that label instead of observing it is exactly what put two withdrawn series on this page, and the reason they were deleted from the feed rather than annotated on it.

Provenance.

  • run 20260821T095952Z-09524a1-47177732, captured 2026-08-21T10:21:49.972Z
  • host Apple M5 · darwin/arm64 · node v26.5.1
  • causl-ts@causlts/core@0.3.3 · engine js-ssot attested
  • causl-wasm@causl/causl-wasm-ts@0.7.0 · engine rust-ssot attested
  • jotaijotai@2.20.2
  • mobxmobx@6.16.1
  • redux-rtk@reduxjs/toolkit@2.9.0

The full aggregate, its four integrity digests and the per-runner inputs are archived in causl-bench under runners/archive/20260821T095952Z-09524a1-47177732/. The raw rows behind every table below are in history.json.

Cross-library comparison: 5 runners, 21 ranked groups

Median wall-time in milliseconds for one step on a world built fresh for that step. Fastest in each row is marked; rows whose winner falls inside the tie band read tied, and rows the aggregator refused to crown read withheld. Rank comes from the aggregate, not from sorting these medians. It applies the tie band and the work-equivalence exclusions, and a table that sorted for itself would quietly disagree with both.

Cellcausl-tscausl-wasmjotaimobxredux-rtkfastest
avoidablePropagation @ 100000.1720 low0.6739 high0.2916 high0.0755 high0.2639 highmobx
dynamic-dep-flip @ 1000.0072 high0.0551 high0.0111 high0.0087 high0.0102 lowcausl-ts
dynamic-dep-flip @ 100000.0087 low0.0716 high0.0117 low0.0082 high0.0097 lowmobx
equality-cutoff-noop @ 100000.0024 high0.0249 high3.464 high0.1315 high2.425 highcausl-ts
linear-chain @ 1000.0409 low0.3299 high0.1090 low0.0339 high0.0293 highredux-rtk
linear-chain @ 10000.6082 low2.390 high0.8975 high0.1649 high0.2645 highmobx
linear-chain @ 100004.149 high26.96 highinapplicableinapplicableinapplicablecausl-ts
mux @ 1003.484 high24.77 high10.43 high2.188 high4.154 highmobx
mux @ 10003.475 low24.67 high10.30 high2.217 high4.171 highmobx
mux @ 100003.561 high24.75 high10.42 high2.187 high4.148 highmobx
mux-ballast @ 1003.543 high24.67 high10.41 high2.249 high4.139 highmobx
mux-ballast @ 10003.507 high25.68 high10.42 high2.259 high4.169 highmobx
mux-ballast @ 100003.687 high27.33 high10.50 high2.502 high4.295 highmobx
op-commit-noderived-1k @ 10000.2951 low4.476 high0.3634 low0.0326 high0.5301 lowmobx
op-input-create-1k @ 10000.1531 high1.191 low0.0464 high0.0975 low85.33 highjotai
scrolling-viewport @ 1000.0036 high0.0271 high0.0480 high0.0080 high0.4586 highcausl-ts
subscriber-churn-1k @ 1000.0229 high0.3899 low6.671 high0.1876 high0.8736 highcausl-ts
subscriber-churn-1k @ 10000.1771 high7.163 high6.448 high1.233 low3.779 highcausl-ts
subscriber-churn-1k @ 100000.1719 high6.898 high6.339 high14.60 high61.04 highcausl-ts
unstable @ 1000.0092 high0.0666 low0.0153 high0.0081 high0.0075 highredux-rtk
unstable @ 100000.2758 high0.7856 low0.4080 high0.1020 low0.0678 highredux-rtk

Full results: every cell, every runner

One block per runner, 90 cells each. p95 is the 95th percentile of the repetitions the runner kept, not a model fitted to the median. 143 of 395 measured cells declare themselves low-confidence; the chip on each row says which, and a low-confidence median is a number whose own driver says not to quote it.

causl-ts: 84 measured, 6 not measured · @causlts/core@0.3.3
Cellmedian (ms)p95 (ms) 95% intervalCoVconfidence recomputes
adversarial-fanin-100 @ 1000.02300.02830.0219–0.025029.5%low1
adversarial-fanin-100 @ 10000.02260.05590.0210–0.024146.9%low1
adversarial-fanin-100 @ 100000.02060.02520.0197–0.02189.3%high1
async-race @ 100inapplicable: the engine commits synchronously; an async race has no analogue without inventing a scheduler that is not part of the library
async-race @ 1000inapplicable: the engine commits synchronously; an async race has no analogue without inventing a scheduler that is not part of the library
async-race @ 10000inapplicable: the engine commits synchronously; an async race has no analogue without inventing a scheduler that is not part of the library
avoidablePropagation @ 1000.00980.01290.0077–0.010523.4%low2
avoidablePropagation @ 10000.02440.02910.0231–0.02558.8%high2
avoidablePropagation @ 100000.17200.22710.1588–0.192315.1%low2
batch-commit @ 1000.04550.07420.0439–0.048819.1%low1
batch-commit @ 10001.2581.3061.250–1.2972.3%high1
batch-commit @ 1000097.02113.0196.50–110.227.6%low1
commit-firehose-1000-subs @ 1000.02160.02450.0199–0.022810.0%high1
commit-firehose-1000-subs @ 10000.02080.02440.0201–0.02189.7%high1
commit-firehose-1000-subs @ 100000.02020.02260.0200–0.02096.1%high1
commit-firehose-subs @ 1000.00710.00750.0070–0.00729.6%high1
commit-firehose-subs @ 10000.02030.02370.0195–0.02208.1%high1
commit-firehose-subs @ 100000.10470.11230.1031–0.10599.8%high1
commit-idle-subs @ 1000.00330.00370.0032–0.00346.4%high0
commit-idle-subs @ 10000.00370.00450.0034–0.003912.0%high0
commit-idle-subs @ 100000.00400.00470.0036–0.004313.5%high0
diamond @ 1000.00820.00960.0077–0.008510.0%high3
diamond @ 10000.00810.01180.0075–0.009323.7%low3
diamond @ 100000.00780.01010.0070–0.008625.9%low3
dynamic-dep-flip @ 1000.00720.00820.0068–0.00748.9%high1
dynamic-dep-flip @ 10000.00730.00850.0069–0.007611.0%high1
dynamic-dep-flip @ 100000.00870.01560.0082–0.009274.1%low1
equality-cutoff @ 1000.01350.01710.0131–0.014411.7%high10
equality-cutoff @ 10000.04640.05260.0443–0.048319.1%low100
equality-cutoff @ 100000.62790.67860.6219–0.636818.1%low1000
equality-cutoff-fanout-10k @ 1000.02080.02170.0204–0.02102.8%high1
equality-cutoff-fanout-10k @ 10000.72880.74170.7255–0.73141.5%high1
equality-cutoff-fanout-10k @ 1000091.9098.4891.62–92.943.0%high1
equality-cutoff-noop @ 1000.00220.00280.0019–0.002515.7%low0
equality-cutoff-noop @ 10000.00210.00250.0020–0.00239.4%high0
equality-cutoff-noop @ 100000.00240.00280.0023–0.00259.1%high0
linear-chain @ 1000.04090.04910.0384–0.04309.9%low100
linear-chain @ 10000.60820.65910.4197–0.625620.0%low1000
linear-chain @ 100004.1494.2734.038–4.2184.3%high10000
mixed-editor-60s-seed42 @ 1000.00470.00530.0046–0.00517.0%high1
mixed-editor-60s-seed42 @ 10000.00620.00770.0059–0.006614.2%high1
mixed-editor-60s-seed42 @ 100000.01380.01600.0131–0.014911.2%high1
mixed-editor-60s-seed42-50k @ 1000.02580.02920.0246–0.02707.9%high1
mixed-editor-60s-seed42-50k @ 10000.02480.02940.0237–0.02589.5%high1
mixed-editor-60s-seed42-50k @ 100000.02600.03400.0251–0.028415.0%low1
mux @ 1003.4843.6923.467–3.5413.1%high10200
mux @ 10003.4753.6943.435–3.5133.6%low10200
mux @ 100003.5613.8043.537–3.5864.3%high10200
mux-ballast @ 1003.5433.7163.521–3.6333.8%high10200
mux-ballast @ 10003.5073.5723.494–3.5381.0%high10200
mux-ballast @ 100003.6873.7573.679–3.7081.5%high10200
op-commit-noderived-1k @ 10000.29510.32740.2892–0.31885.9%low0
op-commit-rollback-1k @ 10008.5108.5858.468–8.5260.7%high499
op-derived-create-1k-fresh @ 10000.52920.56570.5171–0.54246.2%high1000
op-derived-recompute-1k @ 10000.61890.67810.6148–0.629017.8%low1000
op-derived-rollback-1k @ 10000.73630.76430.7189–0.75196.1%low1000
op-input-create-1k @ 10000.15310.15740.1498–0.15457.9%high0
op-no-subscriber-firehose-1k @ 10000.33780.38250.3350–0.364910.1%low0
op-phase-d-bfs-1k @ 10001.3421.3651.341–1.3561.9%high1001
op-read-cold-1k @ 10000.01840.01910.0166–0.018813.6%low0
op-rust-bridge-floor-1k @ 1000inapplicable: no Rust bridge exists on the TypeScript engine
op-subscribe-dispose-1k-pairs @ 10000.20060.22060.1680–0.210612.3%low0
op-tx-set-equal-1k @ 10000.16560.17380.1632–0.16706.2%high0
op-tx-set-isolated-1k @ 10000.31350.35980.3113–0.34676.3%low0
op-tx-shadow-read-1k @ 1000inapplicable: the read-shadow path is not reachable from the public API: the transaction object handed to `commit` exposes only `set` (probed at runtime on 0.3.x, with no `get`). The original cell reached an internal `tx.get`, so measuring it would require an engine change, not a runner change
op-wasm-boundary-1k @ 1000inapplicable: this runner is the TypeScript engine, so there is no JS/WASM boundary to cross. The original cell measured `wasmCommitStub`, a Phase-0 stub whose package is gitignored and absent, so it never measured a real bridge either
readFraction @ 1000.06620.07120.0631–0.06745.8%low200
readFraction @ 10000.51440.55360.4890–0.52565.6%high2000
readFraction @ 100005.8506.0155.802–5.8981.7%high20000
repeatedObservers @ 1000.00650.00720.0061–0.00677.1%low1
repeatedObservers @ 10000.01630.02080.0150–0.017414.4%high1
repeatedObservers @ 100000.08140.08390.0812–0.08161.2%high1
scrolling-viewport @ 1000.00360.00430.0035–0.003710.3%high0
scrolling-viewport @ 10000.00330.00470.0031–0.003619.1%low0
scrolling-viewport @ 100000.00420.00570.0039–0.004419.3%low0
scrolling-viewport-derived @ 1000.00550.00970.0051–0.006625.2%low1
scrolling-viewport-derived @ 10000.00610.00760.0057–0.006816.0%low1
scrolling-viewport-derived @ 100000.00600.00750.0059–0.006414.1%high1
spreadsheet-100x100 @ 1000.02390.02650.0231–0.02466.5%high1
spreadsheet-100x100 @ 10000.02300.02530.0222–0.02336.5%high1
spreadsheet-100x100 @ 100000.02370.02480.0233–0.02435.0%high1
subscriber-churn-1k @ 1000.02290.02460.0224–0.02353.9%high0
subscriber-churn-1k @ 10000.17710.17970.1757–0.17772.2%high0
subscriber-churn-1k @ 100000.17190.17600.1689–0.17313.6%high0
subscriber-churn-derived @ 1000.02680.02830.0262–0.027212.3%high0
subscriber-churn-derived @ 10000.20070.21040.1989–0.20713.2%low0
subscriber-churn-derived @ 100000.20220.21050.1995–0.20342.6%high0
unstable @ 1000.00920.01140.0090–0.01009.7%high3
unstable @ 10000.02390.03810.0225–0.027323.5%low21
unstable @ 100000.27580.28880.2722–0.28114.4%high201
causl-wasm: 84 measured, 6 not measured · @causl/causl-wasm-ts@0.7.0
Cellmedian (ms)p95 (ms) 95% intervalCoVconfidence recomputes
adversarial-fanin-100 @ 1000.07460.08200.0705–0.07908.4%low1
adversarial-fanin-100 @ 10000.07190.09340.0677–0.079014.0%high1
adversarial-fanin-100 @ 100000.07230.08230.0700–0.07487.6%low1
async-race @ 100inapplicable: the engine commits synchronously; an async race has no analogue without inventing a scheduler that is not part of the library
async-race @ 1000inapplicable: the engine commits synchronously; an async race has no analogue without inventing a scheduler that is not part of the library
async-race @ 10000inapplicable: the engine commits synchronously; an async race has no analogue without inventing a scheduler that is not part of the library
avoidablePropagation @ 1000.05080.06560.0492–0.058246.2%low2
avoidablePropagation @ 10000.12130.13270.1193–0.12436.5%high2
avoidablePropagation @ 100000.67390.70220.6676–0.68132.3%high2
batch-commit @ 1000.20600.22890.1986–0.21916.9%high1
batch-commit @ 10001.4101.5071.397–1.4573.7%high1
batch-commit @ 1000012.4512.9212.40–12.531.6%high1
commit-firehose-1000-subs @ 1000.35840.39350.3513–0.37024.8%high1
commit-firehose-1000-subs @ 10000.35230.38660.3445–0.36896.0%high1
commit-firehose-1000-subs @ 100000.35670.40720.3515–0.36606.5%high1
commit-firehose-subs @ 1000.08790.09970.0843–0.09048.7%high1
commit-firehose-subs @ 10000.35300.38990.3421–0.36465.4%high1
commit-firehose-subs @ 100001.7511.8221.731–1.7782.2%high1
commit-idle-subs @ 1000.02670.03210.0261–0.028312.8%high0
commit-idle-subs @ 10000.02620.02790.0246–0.02676.2%high0
commit-idle-subs @ 100000.05480.06370.0528–0.058910.1%high0
diamond @ 1000.05700.08030.0556–0.065315.7%low3
diamond @ 10000.06130.07040.0583–0.063512.0%high3
diamond @ 100000.05840.07500.0554–0.066012.6%high3
dynamic-dep-flip @ 1000.05510.06900.0510–0.057712.3%high2
dynamic-dep-flip @ 10000.05150.07010.0504–0.055415.7%low2
dynamic-dep-flip @ 100000.07160.08050.0681–0.07447.6%high2
equality-cutoff @ 1000.09320.10590.0871–0.09889.9%high10
equality-cutoff @ 10000.32970.36300.3225–0.33495.1%high100
equality-cutoff @ 100002.4312.4772.411–2.4421.9%high1000
equality-cutoff-fanout-10k @ 1000.27200.30450.2616–0.27676.0%high1
equality-cutoff-fanout-10k @ 10001.6312.1411.595–1.65813.7%low1
equality-cutoff-fanout-10k @ 100001.6822.1531.650–2.11213.1%high1
equality-cutoff-noop @ 1000.02370.02600.0223–0.02447.7%high0
equality-cutoff-noop @ 10000.02480.03280.0240–0.027915.0%low0
equality-cutoff-noop @ 100000.02490.02700.0245–0.02616.7%high0
linear-chain @ 1000.32990.34680.3276–0.33794.8%high100
linear-chain @ 10002.3902.5172.358–2.4252.6%high1000
linear-chain @ 1000026.9627.4726.80–27.141.5%high10000
mixed-editor-60s-seed42 @ 1000.04400.05080.0423–0.04678.9%high1
mixed-editor-60s-seed42 @ 10000.04620.05360.0451–0.04928.3%low1
mixed-editor-60s-seed42 @ 100000.09770.11930.0944–0.103419.8%low1
mixed-editor-60s-seed42-50k @ 1000.14750.17790.1452–0.15338.6%high1
mixed-editor-60s-seed42-50k @ 10000.14510.16110.1406–0.14926.3%high1
mixed-editor-60s-seed42-50k @ 100000.14780.16350.1461–0.15416.1%high1
mux @ 10024.7725.0024.33–24.921.9%high10200
mux @ 100024.6724.8624.20–24.752.1%high10200
mux @ 1000024.7525.0524.29–24.802.2%high10200
mux-ballast @ 10024.6725.1324.47–24.851.9%high10200
mux-ballast @ 100025.6825.9325.25–25.811.8%high10200
mux-ballast @ 1000027.3327.7226.88–27.482.0%high10200
op-commit-noderived-1k @ 10004.4764.7854.430–4.5054.4%high0
op-commit-rollback-1k @ 10006.6047.0246.392–6.7944.4%low499
op-derived-create-1k-fresh @ 10001.9832.1501.949–2.0205.0%low1000
op-derived-recompute-1k @ 10002.4012.4852.377–2.4322.1%high1000
op-derived-rollback-1k @ 100028.9129.6828.76–29.053.4%high1000
op-input-create-1k @ 10001.1911.3091.175–1.2164.8%low0
op-no-subscriber-firehose-1k @ 10004.7325.1024.580–4.8204.2%low0
op-phase-d-bfs-1k @ 10002.5542.6492.523–2.5722.2%high1001
op-read-cold-1k @ 10000.06720.10510.0564–0.095027.9%low0
op-rust-bridge-floor-1k @ 1000inapplicable: same Phase-0 stub seam as op-wasm-boundary-1k. The bridge floor is a TIME and the attestation reports a COUNT, so the attestation cannot cover it: this cell is an outstanding gap, not a satisfied one. Measuring it needs a real round-trip against the shipped bridge
op-subscribe-dispose-1k-pairs @ 10001.9382.1151.927–2.0483.8%low0
op-tx-set-equal-1k @ 10003.2773.6823.263–3.3284.8%low0
op-tx-set-isolated-1k @ 10004.6595.1804.602–4.9316.0%low0
op-tx-shadow-read-1k @ 1000inapplicable: the read-shadow path is not reachable from the public API: the transaction object handed to `commit` exposes only `set` (probed at runtime on 0.3.x, with no `get`). The original cell reached an internal `tx.get`, so measuring it would require an engine change, not a runner change
op-wasm-boundary-1k @ 1000inapplicable: this measured `wasmCommitStub`, a Phase-0 stub seam whose package (wasm-stub-pkg/) is gitignored and absent. It never measured the real bridge, and resurrecting it would measure a stub rather than the engine. NOTE: the attestation does NOT substitute for this: it counts crossings and never times them, so the boundary COST is currently unmeasured, not covered
readFraction @ 1000.55140.59530.5394–0.56014.3%low200
readFraction @ 10004.7294.8444.700–4.7691.4%high2000
readFraction @ 1000055.5157.5755.17–55.972.0%high20000
repeatedObservers @ 1000.04350.05360.0426–0.045211.5%high1
repeatedObservers @ 10000.05900.07030.0562–0.062812.6%high1
repeatedObservers @ 100000.16860.17470.1661–0.17122.4%high1
scrolling-viewport @ 1000.02710.03100.0264–0.02868.6%high0
scrolling-viewport @ 10000.02800.03400.0271–0.02889.7%high0
scrolling-viewport @ 100000.05780.09330.0550–0.066524.6%low0
scrolling-viewport-derived @ 1000.04470.05320.0422–0.047710.0%high1
scrolling-viewport-derived @ 10000.04710.06260.0430–0.050815.2%low1
scrolling-viewport-derived @ 100000.06670.08390.0637–0.072612.7%low1
spreadsheet-100x100 @ 1000.10600.13910.1019–0.110115.5%low1
spreadsheet-100x100 @ 10000.10230.10730.0997–0.10385.6%high1
spreadsheet-100x100 @ 100000.10370.11310.0975–0.10717.6%high1
subscriber-churn-1k @ 1000.38990.43750.3811–0.40805.8%low0
subscriber-churn-1k @ 10007.1637.2987.044–7.2051.6%high0
subscriber-churn-1k @ 100006.8987.0226.869–6.9421.3%high0
subscriber-churn-derived @ 1000.48300.51100.4742–0.49083.4%high0
subscriber-churn-derived @ 10007.3197.5237.278–7.4141.7%high0
subscriber-churn-derived @ 100007.3257.5527.258–7.4002.2%high0
unstable @ 1000.06660.07290.0649–0.068213.1%low4
unstable @ 10000.15320.17870.1503–0.16689.4%high22
unstable @ 100000.78560.82070.7751–0.79623.1%low202
jotai: 73 measured, 17 not measured · jotai@2.20.2
Cellmedian (ms)p95 (ms) 95% intervalCoVconfidence recomputes
adversarial-fanin-100 @ 100skipped: measuring this cell to the declared protocol projects to 570s, above the 240s cell budget: one fresh-world cycle costs 18994.4 ms and the protocol needs at least 30 of them. This scenario's setup dominates its measured step, so the honest options are to raise --cell-budget-ms or to split setup from step in the scenario, not to measure fewer than 20 repetitions.
adversarial-fanin-100 @ 1000skipped: measuring this cell to the declared protocol projects to 591s, above the 240s cell budget: one fresh-world cycle costs 19699.7 ms and the protocol needs at least 30 of them. This scenario's setup dominates its measured step, so the honest options are to raise --cell-budget-ms or to split setup from step in the scenario, not to measure fewer than 20 repetitions.
adversarial-fanin-100 @ 10000skipped: measuring this cell to the declared protocol projects to 571s, above the 240s cell budget: one fresh-world cycle costs 19028.9 ms and the protocol needs at least 30 of them. This scenario's setup dominates its measured step, so the honest options are to raise --cell-budget-ms or to split setup from step in the scenario, not to measure fewer than 20 repetitions.
async-race @ 1000.00530.00730.0049–0.005617.6%low0
async-race @ 10000.00510.00820.0049–0.005521.5%low0
async-race @ 100000.00530.00670.0051–0.005613.6%high0
avoidablePropagation @ 1000.01450.02360.0131–0.015845.7%low2
avoidablePropagation @ 10000.03920.04180.0378–0.04035.7%high2
avoidablePropagation @ 100000.29160.30240.2838–0.29713.8%high2
batch-commit @ 1000.04450.04990.0440–0.04575.8%high0
batch-commit @ 10000.36640.44990.3479–0.407812.0%low0
batch-commit @ 100003.8313.9923.766–3.8842.5%high0
commit-firehose-1000-subs @ 100inapplicable: jotai has exactly one subscription primitive, `store.sub(atom, cb)`, which fires when that atom's value changes. The scenario needs two kinds the library does not have: a read-projection registrant over a cold node that must never fire, and a commit-level registrant that fires on every commit regardless of which node changed. jotai has no commit boundary to subscribe to, so the property being measured, per-projection dispatch being O(1) rather than O(registrations), has no expressible analogue. Simulating it with a hand-rolled "commit counter" atom would measure the simulation, not jotai.
commit-firehose-1000-subs @ 1000inapplicable: jotai has exactly one subscription primitive, `store.sub(atom, cb)`, which fires when that atom's value changes. The scenario needs two kinds the library does not have: a read-projection registrant over a cold node that must never fire, and a commit-level registrant that fires on every commit regardless of which node changed. jotai has no commit boundary to subscribe to, so the property being measured, per-projection dispatch being O(1) rather than O(registrations), has no expressible analogue. Simulating it with a hand-rolled "commit counter" atom would measure the simulation, not jotai.
commit-firehose-1000-subs @ 10000inapplicable: jotai has exactly one subscription primitive, `store.sub(atom, cb)`, which fires when that atom's value changes. The scenario needs two kinds the library does not have: a read-projection registrant over a cold node that must never fire, and a commit-level registrant that fires on every commit regardless of which node changed. jotai has no commit boundary to subscribe to, so the property being measured, per-projection dispatch being O(1) rather than O(registrations), has no expressible analogue. Simulating it with a hand-rolled "commit counter" atom would measure the simulation, not jotai.
commit-firehose-subs @ 100inapplicable: This cell sweeps the number of causl subscribers attached to one derived and commits once, which is a causl-only question: it exists to price causl's own notification path against its own history, not to rank libraries. jotai has no commit boundary and no derived-with-observers shape to sweep, so measuring anything here would be measuring a different thing under the same name.
commit-firehose-subs @ 1000inapplicable: This cell sweeps the number of causl subscribers attached to one derived and commits once, which is a causl-only question: it exists to price causl's own notification path against its own history, not to rank libraries. jotai has no commit boundary and no derived-with-observers shape to sweep, so measuring anything here would be measuring a different thing under the same name.
commit-firehose-subs @ 10000inapplicable: This cell sweeps the number of causl subscribers attached to one derived and commits once, which is a causl-only question: it exists to price causl's own notification path against its own history, not to rank libraries. jotai has no commit boundary and no derived-with-observers shape to sweep, so measuring anything here would be measuring a different thing under the same name.
commit-idle-subs @ 100inapplicable: This cell sweeps the number of causl subscribers attached to one derived and commits once, which is a causl-only question: it exists to price causl's own notification path against its own history, not to rank libraries. jotai has no commit boundary and no derived-with-observers shape to sweep, so measuring anything here would be measuring a different thing under the same name.
commit-idle-subs @ 1000inapplicable: This cell sweeps the number of causl subscribers attached to one derived and commits once, which is a causl-only question: it exists to price causl's own notification path against its own history, not to rank libraries. jotai has no commit boundary and no derived-with-observers shape to sweep, so measuring anything here would be measuring a different thing under the same name.
commit-idle-subs @ 10000inapplicable: This cell sweeps the number of causl subscribers attached to one derived and commits once, which is a causl-only question: it exists to price causl's own notification path against its own history, not to rank libraries. jotai has no commit boundary and no derived-with-observers shape to sweep, so measuring anything here would be measuring a different thing under the same name.
diamond @ 1000.01010.01190.0100–0.01068.3%high3
diamond @ 10000.01140.01250.0111–0.01206.2%high3
diamond @ 100000.01130.01300.0111–0.01166.0%high3
dynamic-dep-flip @ 1000.01110.01360.0106–0.011613.4%high1
dynamic-dep-flip @ 10000.01030.01400.0100–0.010816.1%low1
dynamic-dep-flip @ 100000.01170.01570.0114–0.012523.6%low1
equality-cutoff @ 1000.05500.05770.0544–0.05563.8%high0
equality-cutoff @ 10000.40950.47880.3734–0.454911.3%low0
equality-cutoff @ 100003.5673.6903.518–3.6012.7%high0
equality-cutoff-fanout-10k @ 1000.08520.09240.0833–0.08546.5%high10
equality-cutoff-fanout-10k @ 10000.65080.76940.6189–0.738410.0%low100
equality-cutoff-fanout-10k @ 100007.7047.7787.551–7.7471.7%high1000
equality-cutoff-noop @ 1000.05360.05910.0526–0.05435.0%high0
equality-cutoff-noop @ 10000.41990.49020.3845–0.461111.6%low0
equality-cutoff-noop @ 100003.4643.5383.450–3.5001.2%high0
linear-chain @ 1000.10900.12070.1079–0.11485.0%low100
linear-chain @ 10000.89750.93550.8866–0.90432.5%high1000
linear-chain @ 10000inapplicable: jotai evaluates a derived chain by mutual recursion through readAtomState, with no iterative fallback. At a chain depth of 10000 the first mounted read exhausts V8's call stack (RangeError: Maximum call stack size exceeded) on the default 984 KiB main stack, so the graph cannot be constructed at all. There is nothing to measure, at any number of repetitions. Shallower scales of the same scenario are measured normally.
mixed-editor-60s-seed42 @ 100424042634233–42460.3%low1924529
mixed-editor-60s-seed42 @ 1000383938793828–38440.5%low1924529
mixed-editor-60s-seed42 @ 10000422742554221–42390.4%low1924529
mixed-editor-60s-seed42-50k @ 100937.22945.35935.68–938.580.5%high237404
mixed-editor-60s-seed42-50k @ 1000938.30942.97934.75–940.040.4%high237404
mixed-editor-60s-seed42-50k @ 10000993.80998.81993.08–994.520.4%low237404
mux @ 10010.4310.6310.37–10.481.0%high10200
mux @ 100010.3010.4610.28–10.350.9%high10200
mux @ 1000010.4210.7610.37–10.541.6%high10200
mux-ballast @ 10010.4110.5610.32–10.441.4%high10200
mux-ballast @ 100010.4210.6510.36–10.531.4%high10200
mux-ballast @ 1000010.5010.6910.48–10.611.0%high10200
op-commit-noderived-1k @ 10000.36340.49200.3222–0.426918.2%low0
op-commit-rollback-1k @ 1000inapplicable: jotai has no transaction and no atomic rollback. `store.set` commits per call, and a throw out of a write function or a derived getter leaves the already-written atoms written. The cell can only be imitated by catching the throw and re-issuing the prior value by hand, which measures a user-space workaround rather than an engine primitive, so it is reported as absent rather than as a comparable number.
op-derived-create-1k-fresh @ 10000.04850.05120.0408–0.049526.4%low0
op-derived-recompute-1k @ 1000867.00882.11863.82–872.110.9%high1000000
op-derived-rollback-1k @ 1000inapplicable: jotai has no transaction and no atomic rollback. `store.set` commits per call, and a throw out of a write function or a derived getter leaves the already-written atoms written. The cell can only be imitated by catching the throw and re-issuing the prior value by hand, which measures a user-space workaround rather than an engine primitive, so it is reported as absent rather than as a comparable number.
op-input-create-1k @ 10000.04640.04850.0456–0.047213.2%high0
op-no-subscriber-firehose-1k @ 10000.66080.73430.5207–0.693418.1%low0
op-phase-d-bfs-1k @ 1000874.61892.91871.71–881.761.2%high1000000
op-read-cold-1k @ 10000.10330.11950.0982–0.10699.3%high0
op-rust-bridge-floor-1k @ 1000inapplicable: measures a causl backend seam (the JS<->Rust/WASM call boundary). jotai has no WASM backend and no operation to compare against; any number published here would be a different measurement wearing the same column header.
op-subscribe-dispose-1k-pairs @ 10000.45360.47500.4488–0.46348.6%high0
op-tx-set-equal-1k @ 10000.13170.17520.1308–0.132911.3%high0
op-tx-set-isolated-1k @ 10000.57000.61330.5183–0.588512.1%high0
op-tx-shadow-read-1k @ 10000.81210.90260.7715–0.853310.8%low0
op-wasm-boundary-1k @ 1000inapplicable: measures a causl backend seam (the JS<->Rust/WASM call boundary). jotai has no WASM backend and no operation to compare against; any number published here would be a different measurement wearing the same column header.
readFraction @ 1000.02560.02700.0251–0.02623.1%low40
readFraction @ 10000.20920.22050.2059–0.21112.8%low400
readFraction @ 100001.5831.6071.575–1.5941.2%high4000
repeatedObservers @ 1000.01840.02080.0180–0.01976.3%high1
repeatedObservers @ 10000.07670.11200.0761–0.091618.2%low1
repeatedObservers @ 100000.67610.68050.6730–0.67710.5%high1
scrolling-viewport @ 1000.04800.04970.0475–0.04853.6%high0
scrolling-viewport @ 10000.05630.07260.0554–0.057521.3%low0
scrolling-viewport @ 100000.10560.11300.1030–0.11015.2%high0
scrolling-viewport-derived @ 1000.08890.10180.0868–0.09166.4%high10
scrolling-viewport-derived @ 10000.32690.39060.3223–0.35118.6%low99
scrolling-viewport-derived @ 100000.45900.47640.4508–0.46343.1%high99
spreadsheet-100x100 @ 100717.12721.63715.68–718.120.4%high110481
spreadsheet-100x100 @ 1000713.15718.59711.11–714.330.5%high110481
spreadsheet-100x100 @ 10000717.81724.33716.51–721.050.6%high110481
subscriber-churn-1k @ 1006.6717.6126.460–6.9456.9%high0
subscriber-churn-1k @ 10006.4487.4156.008–6.8119.8%high0
subscriber-churn-1k @ 100006.3397.5436.079–6.5258.6%high0
subscriber-churn-derived @ 1006.6636.8146.633–6.7071.3%high220
subscriber-churn-derived @ 10006.7306.8376.655–6.7831.5%high220
subscriber-churn-derived @ 100006.7046.8386.623–6.7411.5%high220
unstable @ 1000.01530.01870.0141–0.016410.7%high3
unstable @ 10000.06120.06830.0588–0.06227.9%high21
unstable @ 100000.40800.41760.4012–0.41132.8%high201
mobx: 78 measured, 12 not measured · mobx@6.16.1
Cellmedian (ms)p95 (ms) 95% intervalCoVconfidence recomputes
adversarial-fanin-100 @ 10011.9412.0611.90–11.980.7%high10000
adversarial-fanin-100 @ 1000121.05123.57120.82–121.580.8%high100000
adversarial-fanin-100 @ 10000122412311223–12260.6%low1000000
async-race @ 1000.00290.00360.0028–0.003427.7%low0
async-race @ 10000.00270.00370.0025–0.003035.7%low0
async-race @ 100000.00280.00390.0025–0.003130.9%low0
avoidablePropagation @ 1000.00600.01000.0056–0.006324.3%low2
avoidablePropagation @ 10000.01440.01790.0134–0.015613.4%low2
avoidablePropagation @ 100000.07550.08000.0750–0.07642.6%high2
batch-commit @ 1000.00610.00620.0060–0.00623.7%high0
batch-commit @ 10000.03220.03620.0320–0.03446.1%high0
batch-commit @ 100000.27220.28000.2709–0.27441.5%high0
commit-firehose-1000-subs @ 1000.00240.00270.0023–0.00257.3%high0
commit-firehose-1000-subs @ 10000.00230.00290.0021–0.002616.0%low0
commit-firehose-1000-subs @ 100000.00500.00530.0048–0.00516.8%low0
commit-firehose-subs @ 100inapplicable: this sweeps the number of causl subscribers on one derived and commits once, to price causl's notification path against its own history. mobx has no commit boundary, so a mobx reading here would be a different measurement wearing the same id
commit-firehose-subs @ 1000inapplicable: this sweeps the number of causl subscribers on one derived and commits once, to price causl's notification path against its own history. mobx has no commit boundary, so a mobx reading here would be a different measurement wearing the same id
commit-firehose-subs @ 10000inapplicable: this sweeps the number of causl subscribers on one derived and commits once, to price causl's notification path against its own history. mobx has no commit boundary, so a mobx reading here would be a different measurement wearing the same id
commit-idle-subs @ 100inapplicable: the twin of commit-firehose-subs, holding a population of observers the commit cannot reach. It exists to separate the cost of notifying an observer from the cost of having one inside causl, and mobx has no commit boundary to hang it on
commit-idle-subs @ 1000inapplicable: the twin of commit-firehose-subs, holding a population of observers the commit cannot reach. It exists to separate the cost of notifying an observer from the cost of having one inside causl, and mobx has no commit boundary to hang it on
commit-idle-subs @ 10000inapplicable: the twin of commit-firehose-subs, holding a population of observers the commit cannot reach. It exists to separate the cost of notifying an observer from the cost of having one inside causl, and mobx has no commit boundary to hang it on
diamond @ 1000.00890.01180.0084–0.009316.9%low3
diamond @ 10000.00830.01120.0081–0.009415.0%high3
diamond @ 100000.00920.01080.0088–0.009712.3%high3
dynamic-dep-flip @ 1000.00870.01040.0081–0.009313.1%high1
dynamic-dep-flip @ 10000.00850.01180.0080–0.009317.3%low1
dynamic-dep-flip @ 100000.00820.01000.0079–0.008411.5%high1
equality-cutoff @ 1000.00420.00430.0041–0.00434.9%high0
equality-cutoff @ 10000.02200.03480.0202–0.026824.6%low0
equality-cutoff @ 100000.18850.19190.1879–0.18961.2%high0
equality-cutoff-fanout-10k @ 1000.01800.02450.0164–0.019318.8%low10
equality-cutoff-fanout-10k @ 10000.10930.12550.1060–0.11559.5%low100
equality-cutoff-fanout-10k @ 100000.50460.52440.4996–0.51522.5%high1000
equality-cutoff-noop @ 1000.00310.00320.0030–0.00317.6%high0
equality-cutoff-noop @ 10000.01440.01780.0140–0.016118.9%low0
equality-cutoff-noop @ 100000.13150.13740.1313–0.13283.5%high0
linear-chain @ 1000.03390.03930.0335–0.03657.4%high100
linear-chain @ 10000.16490.17370.1643–0.16762.5%high1000
linear-chain @ 10000inapplicable: mobx evaluates a computed chain by mutual recursion through its derivation graph, with no iterative fallback. At a chain depth of 10000 reading the tail exceeds the V8 call stack (RangeError: Maximum call stack size exceeded) on the default 984 KiB main stack, so the graph cannot be read at all. There is nothing to measure, at any number of repetitions. Shallower scales of the same scenario are measured normally.
mixed-editor-60s-seed42 @ 1001.2941.6530.9615–1.42522.6%low639
mixed-editor-60s-seed42 @ 100022.2923.7215.56–22.9918.0%low23724
mixed-editor-60s-seed42 @ 10000153015581519–15451.4%low1934077
mixed-editor-60s-seed42-50k @ 1001.2881.8221.138–1.67722.8%low484
mixed-editor-60s-seed42-50k @ 100013.6114.7513.07–13.858.0%high6861
mixed-editor-60s-seed42-50k @ 10000326.05339.32320.30–335.422.8%high239050
mux @ 1002.1882.2202.164–2.2001.5%high10200
mux @ 10002.2172.2442.199–2.2381.2%high10200
mux @ 100002.1872.2432.167–2.2101.5%high10200
mux-ballast @ 1002.2492.2752.231–2.2571.3%high10200
mux-ballast @ 10002.2592.2892.231–2.2751.3%high10200
mux-ballast @ 100002.5022.6152.441–2.5392.9%high10200
op-commit-noderived-1k @ 10000.03260.03730.0324–0.03306.0%high0
op-commit-rollback-1k @ 10000.98351.0090.9784–0.986721.5%low999
op-derived-create-1k-fresh @ 10000.32720.35720.3038–0.341311.7%low1000
op-derived-recompute-1k @ 10000.16760.17290.1666–0.16921.6%high1000
op-derived-rollback-1k @ 1000inapplicable: mobx has no transactional rollback. A throw from inside a computed propagates to the reader and is cached, but no prior writes are unwound, so there is no rollback envelope to measure
op-input-create-1k @ 10000.09750.11540.0325–0.110139.9%low0
op-no-subscriber-firehose-1k @ 10000.03240.03520.0318–0.03355.8%low0
op-phase-d-bfs-1k @ 1000inapplicable: this isolates the BFS/Kahn propagation walk in the causl engine. mobx propagates by invalidating dependents through reaction links, with no separable topological pass to time
op-read-cold-1k @ 10000.03570.04660.0355–0.041722.4%low1000
op-rust-bridge-floor-1k @ 1000inapplicable: mobx has no Rust bridge
op-subscribe-dispose-1k-pairs @ 10000.03990.06620.0395–0.040025.6%low0
op-tx-set-equal-1k @ 10000.03010.05600.0289–0.047340.2%low0
op-tx-set-isolated-1k @ 10000.03700.04140.0359–0.03896.6%low0
op-tx-shadow-read-1k @ 1000inapplicable: there is no transaction object in mobx, so there is no staged read-shadow to measure. Reads inside an action see values already applied
op-wasm-boundary-1k @ 1000inapplicable: mobx is pure JavaScript with no WebAssembly boundary to cross
readFraction @ 1000.01070.01570.0103–0.011755.9%low40
readFraction @ 10000.06490.07670.0564–0.065913.0%low400
readFraction @ 100000.40620.41300.4030–0.40791.5%high4000
repeatedObservers @ 1000.00610.00700.0058–0.00648.8%low1
repeatedObservers @ 10000.00800.01040.0077–0.009016.3%low1
repeatedObservers @ 100000.02390.04580.0238–0.025231.7%low1
scrolling-viewport @ 1000.00800.00870.0079–0.00815.7%high0
scrolling-viewport @ 10000.00700.00900.0069–0.008011.9%high0
scrolling-viewport @ 100000.01150.01360.0114–0.01228.8%high0
scrolling-viewport-derived @ 1000.11240.12100.1100–0.11624.7%low100
scrolling-viewport-derived @ 10000.11370.12100.1114–0.11464.2%high100
scrolling-viewport-derived @ 100000.05690.06250.0561–0.057744.4%low100
spreadsheet-100x100 @ 1000.72520.75470.7220–0.73312.1%high1119
spreadsheet-100x100 @ 10007.1667.3297.117–7.1991.2%high11125
spreadsheet-100x100 @ 1000067.0169.1166.27–67.451.9%high110551
subscriber-churn-1k @ 1000.18760.19380.1861–0.18912.8%high0
subscriber-churn-1k @ 10001.2331.4111.228–1.2947.6%low0
subscriber-churn-1k @ 1000014.6015.2114.58–14.791.8%high0
subscriber-churn-derived @ 1003.3323.5462.561–3.41816.3%low1303
subscriber-churn-derived @ 100016.8321.7214.39–19.7118.9%low1500
subscriber-churn-derived @ 10000152.57158.83151.81–154.531.9%high1500
unstable @ 1000.00810.00980.0077–0.008410.1%high3
unstable @ 10000.01920.02400.0189–0.019713.4%high21
unstable @ 100000.10200.13960.0955–0.124619.0%low201
redux-rtk: 76 measured, 14 not measured · @reduxjs/toolkit@2.9.0
Cellmedian (ms)p95 (ms) 95% intervalCoVconfidence recomputes
adversarial-fanin-100 @ 10016.7616.8616.74–16.800.4%high10000
adversarial-fanin-100 @ 1000156615801564–15700.5%low100000
adversarial-fanin-100 @ 10000skipped: projected ~1.0e+10 input-selector evaluations per repetition (10000 commits × 10000 derivations × fan-in 100). Redux has no per-node invalidation: every dispatch re-runs every mounted selector's input functions, so this is quadratic in scale by construction. Exceeds this runner's 5.0e+8-operation per-repetition budget.
async-race @ 1000.00860.01380.0080–0.010625.7%low0
async-race @ 10000.01690.02000.0115–0.018827.4%low0
async-race @ 100000.00870.01130.0083–0.009532.4%low0
avoidablePropagation @ 1000.01080.01530.0105–0.012821.4%low2
avoidablePropagation @ 10000.02880.03260.0283–0.02975.6%high2
avoidablePropagation @ 100000.26390.28280.2600–0.27323.6%high2
batch-commit @ 1000.03790.04140.0372–0.03874.8%high0
batch-commit @ 10000.32190.37170.3123–0.34868.0%low0
batch-commit @ 100002.9853.0742.954–3.0251.8%high0
commit-firehose-1000-subs @ 1000.01870.02300.0179–0.020911.0%high0
commit-firehose-1000-subs @ 10000.16110.17870.1552–0.16777.1%high0
commit-firehose-1000-subs @ 100001.2571.3311.239–1.2703.9%high0
commit-firehose-subs @ 100inapplicable: this sweeps the observer population on one causl derived and commits once, to price causl against its own history. redux-rtk subscribes to the store, not to a derived node, so it has no population to sweep and a reading here would answer a different question under the same name.
commit-firehose-subs @ 1000inapplicable: this sweeps the observer population on one causl derived and commits once, to price causl against its own history. redux-rtk subscribes to the store, not to a derived node, so it has no population to sweep and a reading here would answer a different question under the same name.
commit-firehose-subs @ 10000inapplicable: this sweeps the observer population on one causl derived and commits once, to price causl against its own history. redux-rtk subscribes to the store, not to a derived node, so it has no population to sweep and a reading here would answer a different question under the same name.
commit-idle-subs @ 100inapplicable: this sweeps the observer population on one causl derived and commits once, to price causl against its own history. redux-rtk subscribes to the store, not to a derived node, so it has no population to sweep and a reading here would answer a different question under the same name.
commit-idle-subs @ 1000inapplicable: this sweeps the observer population on one causl derived and commits once, to price causl against its own history. redux-rtk subscribes to the store, not to a derived node, so it has no population to sweep and a reading here would answer a different question under the same name.
commit-idle-subs @ 10000inapplicable: this sweeps the observer population on one causl derived and commits once, to price causl against its own history. redux-rtk subscribes to the store, not to a derived node, so it has no population to sweep and a reading here would answer a different question under the same name.
diamond @ 1000.00800.01150.0073–0.009029.7%low3
diamond @ 10000.00860.01150.0082–0.009313.8%high3
diamond @ 100000.00910.01270.0087–0.009818.3%low3
dynamic-dep-flip @ 1000.01020.01130.0099–0.010818.1%low2
dynamic-dep-flip @ 10000.01030.01270.0100–0.010715.6%low2
dynamic-dep-flip @ 100000.00970.01450.0092–0.010120.0%low2
equality-cutoff @ 1000.03680.04290.0363–0.03736.7%high0
equality-cutoff @ 10000.30770.35150.2944–0.33538.3%low0
equality-cutoff @ 100002.7192.7622.706–2.7402.1%high0
equality-cutoff-fanout-10k @ 1000.04130.04780.0409–0.04397.3%high10
equality-cutoff-fanout-10k @ 10000.32910.35030.3193–0.33804.5%low100
equality-cutoff-fanout-10k @ 100003.1023.1303.084–3.1160.8%high1000
equality-cutoff-noop @ 1000.02960.03650.0289–0.03219.6%high0
equality-cutoff-noop @ 10000.28550.29850.2791–0.29014.5%low0
equality-cutoff-noop @ 100002.4252.4762.413–2.4461.4%high0
linear-chain @ 1000.02930.03190.0286–0.03047.5%high100
linear-chain @ 10000.26450.28980.2592–0.27404.6%high1000
linear-chain @ 10000inapplicable: reselect evaluates a chained selector by recursive invocation, so reading the tail of a 10000-link chain recurses 10000 deep and exhausts the V8 call stack (RangeError: Maximum call stack size exceeded) on the default 984 KiB main stack. That happens during construction, before any timed region, so there is nothing to measure at any number of repetitions. Expressing this depth in redux would mean replacing reselect with a hand-rolled iterative evaluator, which would no longer be a reselect measurement. Shallower scales of the same scenario are measured normally.
mixed-editor-60s-seed42 @ 1002.1272.4492.060–2.2698.2%high112
mixed-editor-60s-seed42 @ 1000173.99175.31173.07–174.600.7%high1035
mixed-editor-60s-seed42 @ 10000skipped: projected ~6.0e+8 operations per repetition (30000 write dispatches × 10000 record keys copied by Immer, plus 30000 × ~10000 graph-global store listeners). Redux has neither a partial-state write nor a per-key subscription, so both terms are architectural; the cell exceeds this runner's 5.0e+8-operation per-repetition budget.
mixed-editor-60s-seed42-50k @ 10012.5412.8112.44–12.701.7%high112
mixed-editor-60s-seed42-50k @ 1000505450795051–50600.3%low1035
mixed-editor-60s-seed42-50k @ 10000skipped: projected ~3.3e+9 operations per repetition (30000 write dispatches × 100000 record keys copied by Immer, plus 30000 × ~10000 graph-global store listeners). Redux has neither a partial-state write nor a per-key subscription, so both terms are architectural; the cell exceeds this runner's 5.0e+8-operation per-repetition budget.
mux @ 1004.1544.2694.143–4.1772.0%high10200
mux @ 10004.1714.3464.139–4.2294.1%high10200
mux @ 100004.1484.4514.092–4.2303.3%high10200
mux-ballast @ 1004.1394.2204.122–4.1671.7%high10200
mux-ballast @ 10004.1694.3574.148–4.1991.8%high10200
mux-ballast @ 100004.2954.3654.285–4.3170.9%high10200
op-commit-noderived-1k @ 10000.53010.56620.4662–0.542313.2%low0
op-commit-rollback-1k @ 10000.78700.86190.7600–0.82404.9%high0
op-derived-create-1k-fresh @ 10000.34310.41630.3388–0.367211.4%low0
op-derived-recompute-1k @ 1000209.74211.93208.88–210.370.8%high1000000
op-derived-rollback-1k @ 1000inapplicable: redux derivations are pull-based and evaluate after `dispatch` has already published the new state, so a throwing selector has no in-flight commit to unwind. The only redux-side number available would be a user-space compensating re-dispatch, which measures rollback *simulation* rather than the primitive this cell names. (The sibling cell `op-commit-rollback-1k` IS measured: a throw inside the reducer body aborts the Immer draft and leaves the store state untouched.)
op-input-create-1k @ 100085.3386.3985.18–85.450.6%high0
op-no-subscriber-firehose-1k @ 100038.3639.0038.24–38.721.1%high0
op-phase-d-bfs-1k @ 1000inapplicable: isolates the BFS + Kahn topological walk inside causl's `recomputeAffected`. Redux has no propagation walk to isolate: dispatch runs one reducer and notifies every listener, and derivation order is whatever order readers happen to pull in. There is no corresponding data structure to probe.
op-read-cold-1k @ 10000.00240.00320.0023–0.002861.5%low0
op-rust-bridge-floor-1k @ 1000inapplicable: measures the bare wasm-bindgen scalar-call floor (`read_input(slot, gen) -> u64`) of the causl Rust bridge. Redux Toolkit is pure JavaScript; there is no bridge and no floor to measure.
op-subscribe-dispose-1k-pairs @ 10000.09060.10000.0874–0.09248.6%low0
op-tx-set-equal-1k @ 10000.25430.26180.2510–0.25712.8%high0
op-tx-set-isolated-1k @ 10000.54360.57050.5382–0.55682.9%low0
op-tx-shadow-read-1k @ 10000.54720.59940.4215–0.557415.0%low0
op-wasm-boundary-1k @ 1000inapplicable: measures the JS↔WASM `commit(state, action)` boundary of the causl backend. Redux Toolkit has no WebAssembly backend and no boundary to cross, so there is no operation to compare against.
readFraction @ 1000.01660.01920.0155–0.01739.0%high40
readFraction @ 10000.11320.11530.1119–0.11402.6%high400
readFraction @ 100000.86681.0110.8520–0.90686.1%low4000
repeatedObservers @ 1000.00820.01030.0080–0.008619.1%low1
repeatedObservers @ 10000.01740.02120.0167–0.01849.2%high1
repeatedObservers @ 100000.11050.12460.1098–0.11606.0%high1
scrolling-viewport @ 1000.45860.46940.4547–0.46311.9%high0
scrolling-viewport @ 10003.4113.5923.370–3.4892.7%low0
scrolling-viewport @ 1000030.8131.6430.73–30.921.0%high0
scrolling-viewport-derived @ 1000.60000.64340.5952–0.61443.4%low100
scrolling-viewport-derived @ 10005.0055.1794.981–5.0282.1%high100
scrolling-viewport-derived @ 1000050.7051.3750.57–50.741.0%high100
spreadsheet-100x100 @ 10063.5864.1163.34–63.920.9%high11085
spreadsheet-100x100 @ 1000557.91600.49556.72–564.083.2%high110481
spreadsheet-100x100 @ 10000544554595431–54550.3%low1107212
subscriber-churn-1k @ 1000.87361.0720.8682–0.88197.7%high0
subscriber-churn-1k @ 10003.7793.8363.762–3.7970.9%high0
subscriber-churn-1k @ 1000061.0461.5760.82–61.160.7%high0
subscriber-churn-derived @ 1001.1811.3251.172–1.2014.2%high204
subscriber-churn-derived @ 10004.3374.3764.324–4.3560.6%high220
subscriber-churn-derived @ 1000058.4259.2458.10–58.711.0%high211
unstable @ 1000.00750.00850.0071–0.007814.7%high3
unstable @ 10000.01340.01810.0126–0.015115.4%low21
unstable @ 100000.06780.07290.0667–0.06864.2%high201
Library limitations

Why some (scenario × library) cells are absent.

Some cells in the comparison table are absent because of structural limits in the comparator library, not because the scenario was selected to disadvantage it. Where a comparator can run a cell, its result is published as-is; where it cannot, the structural reason is stated and the cell renders as (skipped: …) with a reason string after #1293. The fairness-audit discipline (docs/bench/2026-05-12-fairness-audit.md) classifies every skip into one of the three buckets below.

Class 1 · Stack overflow at chain depth

Recursive read-path overflows V8 at linear-chain × 10000.

Affected cells: linear-chain × 10000 for jotai, mobx, redux-toolkit. Causl reports a real median here post-#956.

Each comparator's read path evaluates the chain via mutual recursion through its track/get entry point, so the V8 call stack overflows at depth 10 000. The harnesses throw a typed error up-front, so the doomed setup never runs and the runner records the cell inapplicable, a fact about what the library can express, distinct from skipped (the clock ran out) and failed (it tried and broke). Each cell carries its own reason string, and every one of them is on this page.

Causl's Phase-D driver is iterative end-to-end after #956: the registration walk and the commit-time fix-point both run through an explicit StackFrame[] rather than recursive calls. SPEC §5.1 Amendment 3 pins this as an implementation-detail trail with no contract change: every §3 atomicity and §5.1 phase invariant is preserved byte-identically; only the depth ceiling moves. The asymmetry is structural to the JS engine shape, not the workload, and tracked under #721 part 3 (mobx investigation) / #798 / #922 / #926.

Class 2 · No public API expressible

Scenario's acceptance gate cannot be expressed in the comparator's public surface.

Affected cells: commit-firehose-1000-subs, multi-fetch-race-N10, and op-wasm-boundary-1k, each skipped for jotai, mobx, redux-toolkit.

These scenarios require commit-metadata semantics (subscribeReads vs subscribeCommits split, SPEC §5.1 Phase F), staleness-aware async (a commit-seq surface so a late fetch result can detect that a newer write has landed; see SPEC.async.md), or the WASM-FFI boundary contract (SPEC §17.6) Enterprise. None of the three comparator libraries have an equivalent public-API surface, so the cell's acceptance gate is not architecturally expressible.

The harness throws a typed ExpansionScenarioNotImplementedError with a reason string identifying the public-API gap, deferred explicitly under #843 rather than implementing a cell that runs but cannot answer the question the scenario was constructed to ask (the “loud silence” failure mode).

Class 3 · Expansion not implemented

Catch-all for typed skips that fall outside Classes 1 and 2.

Any cell where the harness throws ExpansionScenarioNotImplementedError for a per-cell reason that does not reduce to a structural stack overflow or to a public-API gap. None are currently active in the published comparison table (every entry today falls into Class 1 or Class 2), but the bucket exists so future expansion scenarios with library-specific gaps have a documented home rather than appearing as a silent drop. The per-cell reason string in comparison_table.md always names the gap; this class is the residual category.

What we measure

Three axes, picked because they fail in the field.

Microbenchmarks lie about real workloads, so each axis is anchored to a scenario that already exists as a runnable demo in the repository. No synthetic loops without a corresponding adopter shape.

Cascade latency

Time-to-coherent-state.

Spreadsheet-diamond and linear-chain scenarios from causl-bench's scenario contract commit a single root edit and measure wall-clock time until every dependent has settled to its glitch-free value. Reported as median + p95 across runs, library by library.

Bundle size

Full-import @causl/core.

Measured against the SPEC §14.2 budget (the 15 KB to 30 KB band with an 18 KB working target) using the same size-limit configuration that already gates every PR. Surfaced via bundle-table.ts and will land on this page in a follow-up.

Memory ceiling

Bounded enumerator under load.

The BFS frontier / traces / races caps are swept across cap settings, plotting states_visited versus wall-clock so adopters can pick a cap without guessing.

For the full methodology behind these numbers (the SPEC §17.5 capability-cost residual band and how to run the bench harness against your own workload), read the Performance usage guide. Opting into the WebAssembly backend Enterprise and its benchmark story are covered in the Enterprise wasm-performance guide.