Long continuous prefix move optimization#5172
Open
JoviDeCroock wants to merge 3 commits into
Open
Conversation
JoviDeCroock
force-pushed
the
long-continuous-prefix
branch
from
July 22, 2026 06:35
35c62d3 to
178ed65
Compare
📊 Tachometer Benchmark ResultsSummaryduration
usedJSHeapSize
Resultscreate10kduration
usedJSHeapSize
filter-listduration
usedJSHeapSize
hydrate1kduration
usedJSHeapSize
many-updatesduration
usedJSHeapSize
replace1kduration
usedJSHeapSize
run-warmup-0
run-warmup-1
run-warmup-2
run-warmup-3
run-warmup-4
run-final
text-updateduration
usedJSHeapSize
tododuration
usedJSHeapSize
update10th1kduration
usedJSHeapSize
|
|
Size Change: +303 B (+0.38%) Total Size: 79.8 kB 📦 View Changed
ℹ️ View Unchanged
|
This was referenced Jul 22, 2026
The probe only applies when the match is more than one position ahead, which the existing matchingIndex > skewedIndex branch already implies once the off-by-one cases are handled first. Nesting it there drops the duplicated comparisons; 5 B brotli smaller.
Covers the paths around the displacement probe: - a far swap where the suffix after the match is shorter than the jump must not trigger it (and the fallback already moves the minimal pair) - displacing more than half the list falls back to moving the shorter suffix, which is also minimal - displacement combined with an appended or removed child - three consecutive displacements to catch skew accumulation issues - a raw text sibling passes the probe's key/type comparison against any old text node; assert such a false positive can only cost extra moves, never correctness
JoviDeCroock
added a commit
that referenced
this pull request
Jul 22, 2026
Mirrors the edge-case coverage added to the v10.x displacement heuristic (#5172), where the minimal-move pass produces identical operation logs for every case: - a far swap moves only the two swapped children - displacing more than half the list moves the shorter suffix - displacement combined with an appended or removed child - three consecutive displacements to catch state accumulation issues - correctness of raw text siblings around displaced keyed children
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
This PR was co-authored by GPT5.6-sol
Summary
Optimize keyed reconciliation when a short prefix is moved to the end of a list.
The existing skew heuristic only recognizes one-position offsets. For a transition like:
Preact moved the long suffix instead of the three displaced prefix nodes. In a 1,000-row benchmark, moving the first three rows caused 997
insertBeforecalls.This change detects when the matched node begins a longer contiguous suffix. It adjusts the skew to leave that suffix in place and moves the shorter prefix when reconciliation reaches it.
Results
Measured with Octane’s
js-framework-reorderbenchmark:displace3displace4displace5displace6displace8For
displace3, DOM moves drop from 997 to 3.Other reorder operations remain effectively unchanged.
Edge-case coverage
Beyond the displacement test itself, the suite now pins down the paths around the probe: a far swap whose following suffix is shorter than the jump must not trigger it, displacing more than half the list falls back to moving the shorter suffix (also minimal), displacement combined with an appended or removed child, three consecutive displacements (skew accumulation), and a raw-text sibling passing the probe's loose key/type comparison — a false positive that can only cost extra moves, never correctness.
Reproducible benchmark
The displacement pattern is now covered by a dedicated benchmark in the benchmarks repo — see preactjs/benchmarks#17 (
reorder1k: move the first 3 of 1,000 keyed rows to the end). Tachometer results on this branch (Chrome headless):update10th1k,create10k, andmany-updatesare unchanged within noise.Relation to v11
v11 generalizes this with a longest-increasing-subsequence pass that computes the minimal set of moves for every reorder shape (middle swaps, intermingled moves, …), at a higher byte cost — see #5174 (reorder1k median 8.3 ms there). The two supersede each other, so this heuristic stays v10.x-only: it is the cheaper fix (+~40 B vs +99 B brotli) for the release branch, while #5174 is the v11 form.