perf(signals): mapArray small-move fast path for keyed reorders - #3227
perf(signals): mapArray small-move fast path for keyed reorders#3227ryansolid wants to merge 3 commits into
Conversation
🦋 Changeset detectedLatest commit: 21152c5 The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Coverage Report for CI Build 33668852541Coverage remained the same at 71.564%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
e98b51c to
fc0a52e
Compare
Merging this PR will degrade performance by 54.86%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ❌ | merge |
75.7 µs | 167.6 µs | -54.86% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing keyed-small-moves (21152c5) with next (87d5338)
…a cost Per-op decomposition of the octane board's stable jfb-reorder deficits (rotate 1.9x, displace3-8 2.1-2.7x, removefirst 3-4x vs octane) profiled to updateKeyedMap: the keyed diff pays O(newLen) regardless of delta — window key-map, four full-length staged arrays, element-copied prefix and suffix — ~50-140µs/op on 1000 rows against ~20µs of actual DOM work. The fast path (trySmallMove, own function — inlining it deoptimized the GENERAL path via the JIT function-size budget, same lesson as the uibench reconcile regression): - PHASE 1, scan only: two-pointer walk records aligned RUNS, realigning at boundaries with bounded lookahead (interleaved splices stack shift offsets past single-step). A compare BUDGET makes hopeless shapes (reverse/shuffle) bail almost immediately with zero allocation. - PHASE 2, commit on success: slice() the live arrays (native memcpy keeps the fresh-identity contract downstream change propagation relies on), copy only shifted runs, patch displaced pairs (O(k^2), k<=32), dispose leftover sources (dif<0). Unmatched destinations (replacements, insertions) bail with nothing staged. - Gated to LARGE trimmed windows: the trims already make plain removals window-cheap; the fast path would only re-walk what they proved. Same-load A/B on 1000 rows: rotatef 140->13µs, rotateb 142->14µs, swap 94->8µs, displace3 54->9µs; removefirst 6.5µs and reverse at parity. Row-signal/custom-key modes adopt through the same compare() rule with per-run setSignal parity to the general path's step-3. New suite pins the semantics: identity preservation across rotate/ displace/swap at jfb scale (1000 rows), replacement/mixed/duplicate windows land the general path correctly, custom-key moves match by key and adopt fresh objects, scrambles beyond the bound and length changes stay correct. signals 1497 / web 726 / solid 576 green. Co-authored-by: Cursor <cursoragent@cursor.com>
Size golf: source-level dedup (shared eq/scan/place helpers) bought almost nothing — brotli already compresses repetition — so the real cut is UNIQUE LOGIC: the fast path now serves only the plain identity-keyed mode (no row signals, no index accessors — the hot For shape); other modes keep the general path. That deletes the compare/keyFn plumbing and half of phase 2: added cost drops 0.62 -> ~0.45 kB brotli in mapArray- bearing bundles, zero elsewhere. The pure-identity hot loop also got FASTER: rotate 10.5 -> 5.8µs, swap 6.2 -> 4.0µs, displace3 7.0 -> 4.6µs, removefirst 3.4µs, reverse at pristine parity. Also removes a latent double-push in the del branch (leftover half-edit; engaged only at deep lookahead distances). All 32 map suites green; full signals 1497 green. Co-authored-by: Cursor <cursoragent@cursor.com>
trySmallMove rides mapArray's keyed path, so every For-bearing bundle pays 430-490 B brotli (A/B-verified against the branch base; non-For bundles pay zero). Buys delta-cost keyed reorders — jfb swap1k −6.5% with the main suite clean. Co-authored-by: Cursor <cursoragent@cursor.com>
fc0a52e to
21152c5
Compare
|
Parking this (not shipping in RC.6): it optimizes the reactive half of a double-reconcile (mapArray diffs items, reconcileArrays re-derives the same moves against the DOM), and we've decided to pursue deleting the double-reconcile instead — a unified keyed |
Summary
trySmallMove) tomapArray's keyed update path: rotations, swaps, and small displacements (≤16 displaced rows) commit as targeted moves instead of full-window rebuilds. Ineligible shapes fall through to the untouched algorithm after a few probes.updateKeyedMaploop stays within JIT inlining budgets (validated: no reverse/shuffle regression).Validation
mapArray-smallmove.test.tssuite pins semantics at 1000-row scale: rotate, displace, swap, remove, replacement, duplicates, mixed shapes, and custom-key exclusion.Notes
maparray-small-move-fast-path.md, patch — prerelease mode).Forconsumers (DOM and universal); does not touchreconcileArrays.Made with Cursor