Feat/signal safety#212
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🟡 Mixed (mostly faster) for
|
| metric | Improvement |
|---|---|
signal:reactive-set-property-by-id-200 |
-82% (160ms) 🏆 |
todo:rename-500 |
-24% (49ms) ⭐ |
signal:computed-unobserved-200x500 |
-21% (5ms) ⭐ |
signal:computed-subscribe-unsubscribe-10k |
-18% (2ms) ⭐ |
signal:reaction-dep-diff-45k |
-13% (5ms) |
signal:computed-chain-10x60k |
-8% (16ms) |
todo:remove-middle-100 |
-7% (4ms) |
hydrate:helper-100-state-change-1k |
-6% (0ms) |
template:subtemplate-helpers-heavy-100x500 |
-5% (2ms) |
signal:reactive-list-filter-1000x300 |
-4% (5ms) |
❌ Slower (4)
Metrics where this PR confidently regressed performance compared to main.
| metric | Regression |
|---|---|
todo:edit-cycle-5 |
+43% (28ms) |
signal:set-same-10m |
+21% (9ms) ❗ |
todo:remove-50-front |
+11% (2ms) |
signal:reactive-stable-fanout-5000x100 |
+4% (4ms) |
🏆 New peaks (2)
These metrics hit a new best on this PR. The most recent candidate is usually the cause.
| metric | improvement | prior peak | likely candidates |
|---|---|---|---|
signal:reactive-set-property-by-id-200 |
70% | 79ba2df |
e52ad9b |
signal:reaction-dep-diff-45k |
8% | 79ba2df |
e52ad9b |
📜 Regressions from peak (20)
These metrics were faster on an earlier push to this PR. The most recent candidate is usually where to look.
⚪ No Change (27)
Metrics where this PR measured within ±2% of main — no meaningful performance change detected.
🔍 Unsure (30)
Inconclusive (2)
The CI crossed ±2% and is wider than this bench's duration usually produces. More samples may settle these.
| metric | Change | Expected Noise |
|---|---|---|
signal:reactive-list-replace-1000x1000 |
-5.1% – -0.9% | ±1% |
signal:reactive-stable-deps-3reads-5000x100 |
-1.4% – +3.4% | ±2% |
Too Fast to Measure Precisely (28)
On benches this short, OS jitter, GC, and JIT pauses drown out anything under 4%. Bigger changes than that still show up.
| metric | Change | Test Time | Expected Noise |
|---|---|---|---|
template:active-indicator-200 |
-2.1% – +2.0% | ~33ms | ±7% |
template:active-indicator-nested-200 |
-3.6% – +0.9% | ~16ms | ±7% |
krausest:append-1k |
-1.4% – +3.0% | ~94ms | ±5% |
krausest:clear-10k |
-1.1% – +2.3% | ~155ms | ±5% |
todo:clear-completed-250 |
-0.5% – +2.5% | ~39ms | ±4% |
signal:flush-fanout-allocation-1000x500 |
-0.7% – +2.1% | ~83ms | ±3% |
signal:reaction-coalesce-400x100 |
-4.1% – -1.1% | ~48ms | ±3% |
signal:reaction-flush-noop-5m |
-13.9% – +11.7% | ~33ms | ±33% |
signal:reactive-fanout-500x1200 |
-1.0% – +2.3% | ~95ms | ±5% |
signal:reactive-multi-read-5x160k |
-7.4% – -1.3% | ~196ms | ±11% |
signal:reactive-push-2000x20 |
-2.9% – +2.1% | ~215ms | ±8% |
signal:reactive-set-index-300 |
-5.1% – +1.8% | ~104ms | ±12% |
todo:remove-50-back |
+0.1% – +2.7% | ~13ms | ±4% |
todo:remove-50-middle |
-3.1% – +1.5% | ~14ms | ±4% |
todo:remove-first-100 |
-1.2% – +3.1% | ~57ms | ±6% |
krausest:remove-row-back-100 |
-10.7% – +9.4% | ~18ms | ±28% |
krausest:remove-row-front-20 |
-1.6% – +12.8% | ~10ms | ±18% |
krausest:remove-row-middle-20 |
-15.4% – +8.7% | ~8ms | ±32% |
template:snippet-args-per-key-100x500 |
-2.8% – -0.4% | ~32ms | ±4% |
template:snippet-in-subtemplate-100x1k |
-5.5% – -1.5% | ~22ms | ±8% |
template:stable-ref-mutate-500 |
-6.2% – +0.1% | ~13ms | ±11% |
signal:sub-unsub-100k |
+0.8% – +7.1% | ~27ms | ±9% |
krausest:swap-rows-20 |
-3.4% – +4.6% | ~101ms | ±10% |
todo:toggle-100 |
+0.7% – +4.8% | ~43ms | ±6% |
todo:toggle-all-200 |
-2.7% – -1.8% | ~895ms | ±1% |
todo:toggle-first-100 |
-3.4% – -0.6% | ~55ms | ±3% |
todo:toggle-middle-100 |
-2.6% – -0.1% | ~42ms | ±3% |
krausest:update-10th-50 |
-4.9% – +6.4% | ~25ms | ±14% |
📖 Bench glossary (71 metrics)
| metric | what it tests |
|---|---|
compiler-micros:ast-walk-15k |
Walks a kitchen-sink AST through optimizeAST 15000 times. Merge, hoist, and recurse pass. |
compiler-micros:parse-cold-complex-200 |
Compiles a feature-dense kitchen-sink template 200 times. Catches parser regressions on uncommon block paths. |
compiler-micros:parse-cold-normal-500 |
Compiles a TodoMVC-style component template 500 times. Headline metric for normal-component compile throughput. |
compiler-micros:snippet-args-5k |
Parses four representative subtemplate-call shapes 5000 times each. Snippet args extraction. |
hydrate:each-100 |
Reassigns the items of a hydrated 1000-item list to a fresh array with the same keys and data. |
hydrate:each-100-mount |
Hydrates a server-rendered 1000-item list and waits for it to become interactive without re-rendering. |
hydrate:helper-100-mount |
Hydrates a 1000-item list where each item calls a helper that reads state shared across the list. |
hydrate:helper-100-state-change-1k |
Walks the shared activeID across every item in a hydrated 1000-item list so two items repaint per cycle. |
krausest:append-1k |
Appends 1000 new rows onto an existing 1000-row table. |
krausest:clear-10k |
Clears a 10000-row table back to empty in a single operation. |
krausest:create-10k |
Renders a fresh 10000-row table into an empty parent at ten times the create-1k scale. |
krausest:create-1k |
Renders a fresh 1000-row table into an empty parent. |
krausest:remove-row-back-100 |
Removes the last row 100 times from a 1000-row table, with no other rows needing to move. |
krausest:remove-row-front-20 |
Removes the first row 20 times from a 1000-row table, with all remaining rows sliding up each time. |
krausest:remove-row-middle-20 |
Removes the middle row 20 times from a 1000-row table, with the rows below it sliding up each time. |
krausest:replace-1k |
Replaces 1000 rows with a fresh 1000-row set, diffing the keyed list against a populated table. |
krausest:select-40 |
Highlights one row at a time across 40 rows so only the previous and newly highlighted rows update. |
krausest:swap-rows-20 |
Swaps the second and second-to-last rows in a 1000-row table, repeated 20 times. |
krausest:update-10th-50 |
Updates the label on every tenth row of a 1000-row table, looped 50 times to lift the work above noise. |
renderer-micros:build-html-string-10k |
Builds the HTML string for a realistic card AST 10000 times. Raw assembly throughput. |
renderer-micros:dom-walker-1000x15 |
Runs bindMarkers across a 1000-node card fragment 15 times. TreeWalker pass and binding dispatch. |
renderer-micros:expr-js-10k |
Evaluates one arithmetic expression and one ternary 10000 times each. JS-eval hot path. |
renderer-micros:expr-lisp-50k |
Evaluates one Lisp-style helper call 50000 times. Parse-cache lookup and helper dispatch. |
renderer-micros:expr-simple-100k |
Evaluates one simple identifier and one dotted path 100000 times each. Property-lookup hot path. |
signal:computed-chain-10x60k |
Propagates a value change from root to leaf through a 10-deep chain of derived signals 60000 times. |
signal:computed-subscribe-unsubscribe-10k |
10000 create-computed + attach-observer + detach cycles. Lifecycle cost the refcount path must keep acceptable. |
signal:computed-unobserved-200x500 |
200 unobserved computed signals, root updated 500 times. Measures the eager-recompute cost the refcount removes. |
signal:flush-fanout-allocation-1000x500 |
500 subscribers fanout across 1000 flush cycles. Each flush spreads pendingReactions; tests per-flush allocation churn. |
signal:reaction-coalesce-400x100 |
Sets one signal 100 times then flushes once across 400 bursts so 100 subscribers wake one time per burst. |
signal:reaction-dep-diff-45k |
Toggles which of two signals a subscriber reads across 45000 cycles. Per-run dep-set diffing. |
signal:reaction-flush-noop-5m |
Calls Reaction.flush() 5000000 times with no pending work. Scheduler dispatch overhead. |
signal:reactive-fanout-500x1200 |
Fans out one signal's value change to 500 subscribers across 1200 successive updates. |
signal:reactive-list-filter-1000x300 |
Changes a search-term signal 300 times, re-scanning a 1000-item list on each change. |
signal:reactive-list-replace-1000x1000 |
Replaces a 1000-item list signal with a fresh 1000-item array and rescans it 1000 times. |
signal:reactive-multi-read-5x160k |
Changes five signals in turn for 32000 rounds with one subscriber reading all five. |
signal:reactive-push-2000x20 |
Appends 20 items onto an empty list signal with a subscriber, across 2000 reset cycles. |
signal:reactive-set-index-300 |
Replaces one item by index in a 1000-item list signal across 300 updates, with a subscriber. |
signal:reactive-set-property-by-id-200 |
Finds an item by id and updates one field in a 1000-item list signal across 200 alternating updates. |
signal:reactive-stable-deps-3reads-5000x100 |
5000 reactions × 3 signals × 100 cycles. Each run clears + re-adds 3 stable dep edges. |
signal:reactive-stable-fanout-5000x100 |
5000 reactions × 1 signal × 100 invalidations. Per-run Set.delete + add on a stable dep edge. |
signal:set-same-10m |
Sets a signal to its current value 10000000 times. Exercises the no-op fast path when nothing changes. |
signal:sub-unsub-100k |
Creates and tears down a subscriber on one signal across 100000 cycles. Subscription churn cost. |
template:active-indicator-200 |
Cycles selectedId across 200 list items. Only the previously and newly active items update their class. |
template:active-indicator-nested-200 |
Cycles currentUrl through 50 leaf urls in a 5×10×4 nav. Only the previously and newly active leaves should update their… |
template:each-mount-1000 |
Mounts a fresh 1000-item each block with five-field items so per-record allocation cost dominates the wall clock. |
template:snippet-args-per-key-100x500 |
Mutates one snippet arg's source across 100 invocations, 500 cycles. Adjacent no-signal expressions stay quiet. |
template:snippet-in-subtemplate-100x1k |
Mutates one subtemplate prop's source across 25 cards each invoking 4 inner snippets, 1000 cycles. Snippet bodies shoul… |
template:stable-ref-mutate-500 |
Replaces one item by index in a 500-item list across 100 cycles. Only that item's expressions re-render. |
template:subtemplate-data-blob-100 |
Mutates one field inside data=expression on 100 children. Every child re-renders by design. |
template:subtemplate-helpers-heavy-100x500 |
100 subtemplates, 4 inner bindings where three call helpers shaped like userland reality — Intl.NumberFormat, Array.fin… |
template:subtemplate-helpers-light-100x500 |
100 subtemplates, 4 inner bindings each calling formatDate / classIf / capitalize, 500 cycles. Mutates one source signa… |
template:subtemplate-reactive-data-100x500 |
Mutates one verbose reactiveData field across 100 child subtemplates, 500 cycles. Only the changed field re-evaluates. |
template:subtemplate-shorthand-props-100x500 |
Mutates one shorthand prop's source across 100 child subtemplates, 500 cycles. Only that prop re-evaluates. |
todo:add-20 |
Appends 20 todo items one at a time, like a user typing entries in a row. |
todo:bulk-add-500 |
Renders 500 todo items added at once from a single data load. |
todo:clear-completed-250 |
Clears 250 completed items from a 500-item list in one action, like clicking clear completed. |
todo:edit-cycle-5 |
Runs 5 full edit-then-save cycles on different items, like editing a row and saving it. |
todo:edit-start-10 |
Enters edit mode on 10 different items in a row, like double-clicking each one. |
todo:filter-cycle-20 |
Cycles through active, completed, and all filters 20 times on a 100-item list. |
todo:remove-50-back |
Deletes 50 items from the end of a 100-item list, one click at a time. |
todo:remove-50-front |
Deletes 50 items from the front of a 100-item list, one click at a time. |
todo:remove-50-middle |
Deletes 50 items from the middle of a 100-item list, one click at a time. |
todo:remove-first-100 |
Deletes the first item 100 times from a 200-item list, with remaining items moving up each time. |
todo:remove-last-100 |
Deletes the last item 100 times from a 200-item list, with no other items needing to move. |
todo:remove-middle-100 |
Deletes the middle item 100 times from a 200-item list, walking halfway through to find each target. |
todo:rename-500 |
Renames items in a 100-item list 500 times via single-field setProperty without editingId co-fires. |
todo:toggle-100 |
Cycles through the first 10 items 10 times each, like a user toggling items repeatedly down a list. |
todo:toggle-all-200 |
Toggles all 100 items completed and back across 200 cycles via the master checkbox. |
todo:toggle-first-100 |
Toggles the first item in a 100-item list 100 times, alternating completed on and off. |
todo:toggle-last-100 |
Toggles the last item in a 100-item list 100 times, alternating completed on and off. |
todo:toggle-middle-100 |
Toggles a middle item in a 100-item list 100 times, alternating completed on and off. |
Sample size: 80 floor / 270 max · Noise floor: ±2% · Timeout: 3min · Wall-clock: 13m14s
Hand coded signal safety because ai is awful at coding