From 2d2a1907a36427285712de74778688955d4e8c4b Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Mon, 14 Sep 2026 13:19:41 -0700 Subject: [PATCH] bench(signals): amortize projection-root-write over 20 commits per body MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three benchmarks share one process and one set of 20k-key fixtures, and each body performed a single commit. V8 emits a one-off ~100k-instruction tier-up chunk that attaches to whichever frame is running when it fires and lands in exactly one of the three windows — at ~540k Ir per root write that is 16-20% of a window, and it moves between windows on unrelated changes. CodSpeed's bisection of #3431 (2026-09-14) made this exact: a change costing ~300 instructions on its own path read as -8.9% on the root write and x2.4 on the untouched nested write; a provably no-op extra call added to `next` read as -5.5% on the root write and +22% on the untouched store setter (537,655 → 508,044 and 513,411 → 627,428 Ir; every reactive-graph symbol bit-identical). Each body now runs COMMITS = 20 commits. The chunk amortizes to <1% of a window — below the 5% gate — and the three stay per-commit comparable, which is the parity this file guards (#3352 derive vs #3044 setter floor). Absolute numbers re-baseline ×20. Co-authored-by: Claude via Cursor Co-authored-by: Cursor --- .../store/projection-root-write.bench.ts | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/packages/signals/tests/store/projection-root-write.bench.ts b/packages/signals/tests/store/projection-root-write.bench.ts index a2c46058a..cc657a539 100644 --- a/packages/signals/tests/store/projection-root-write.bench.ts +++ b/packages/signals/tests/store/projection-root-write.bench.ts @@ -6,10 +6,23 @@ // #3044 prototype overlay. Guard the parity: the projection derive and the // store setter should sit at the same per-commit floor, and the nested // write (a small target — always cheap) is the reference. +// +// Each body runs COMMITS commits, not one. Three benchmarks share this +// process and one set of 20k-key fixtures, and V8 emits a one-off ~100k +// instruction tier-up chunk that attaches to whichever frame is running when +// it fires — landing in exactly one of the three windows. At one commit per +// body (~540k Ir for the root write) that is ~16-20% of a window, and it +// moves between windows on unrelated changes: #3431 (a ~300-instruction +// change on its own path) read as -8.9% here and x2.4 on the untouched +// nested write, and a provably no-op extra call on `next` read as -5.5% here +// and +22% on the untouched store setter (CodSpeed bisection, 2026-09-14). +// Amortized over COMMITS the chunk is noise below the 5% gate; the numbers +// stay per-commit-comparable across the three (all scale together). import { bench, describe } from "vitest"; import { createProjection, createRoot, createSignal, createStore, flush } from "../../src/index.js"; const KEYS = 20_000; +const COMMITS = 20; const seed = () => Object.fromEntries(Array.from({ length: KEYS }, (_, i) => [`k${i}`, { n: i }])); describe(`one root key per commit, ${KEYS}-key record`, () => { @@ -58,21 +71,27 @@ describe(`one root key per commit, ${KEYS}-key record`, () => { }); bench("projection derive: delete + set one ROOT key (#3352)", () => { - bump(++tick); - flush(); + for (let c = 0; c < COMMITS; c++) { + bump(++tick); + flush(); + } }); bench("projection derive: write one NESTED field (reference)", () => { - bumpNested(++tick); - flush(); + for (let c = 0; c < COMMITS; c++) { + bumpNested(++tick); + flush(); + } }); bench("createStore setter: delete + set one root key (#3044 overlay)", () => { - const i = ++tick % KEYS; - setStore(d => { - delete d[`k${i}`]; - d[`k${i}`] = { n: -i }; - }); - flush(); + for (let c = 0; c < COMMITS; c++) { + const i = ++tick % KEYS; + setStore(d => { + delete d[`k${i}`]; + d[`k${i}`] = { n: -i }; + }); + flush(); + } }); });