Skip to content

Avoid traversing retained subtrees - #5183

Merged
JoviDeCroock merged 3 commits into
mainfrom
perf/v11-retained-subtree-bailout
Jul 30, 2026
Merged

Avoid traversing retained subtrees#5183
JoviDeCroock merged 3 commits into
mainfrom
perf/v11-retained-subtree-bailout

Conversation

@JoviDeCroock

@JoviDeCroock JoviDeCroock commented Jul 29, 2026

Copy link
Copy Markdown
Member

Summary

Port of #5182 to v11. On a successful shouldComponentUpdate/memo bailout, set oldDom via getDomSibling(oldVNode) directly in diff()'s bailout block instead of recursively walking the retained child tree through insert(); the existing result branch in diffChildren picks it up. Actual placements still use insert(), which now only handles placement and drops its shouldPlace flag (the moveBefore/isMounting handling is unchanged).

A follow-up commit finishes unifying the cursor: diff() now returns the next sibling cursor for element vnodes too (instead of the element's own DOM node, which diffChildren discarded and re-derived via newDom.nextSibling), so the function-type check in the hot loop goes away. The JSON-injection guard returns UNDEFINED so an invalid child leaves the cursor untouched.

Port of #5182 to v11. On a successful shouldComponentUpdate/memo
bailout, set oldDom via getDomSibling directly in diff()'s bailout
block instead of recursively walking the retained child tree through
insert(); the existing result branch in diffChildren picks it up.
Actual placements still use insert(), which now only handles
placement and drops its shouldPlace flag.

-20 B gzip / -18 B brotli on preact.mjs
@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown

📊 Tachometer Benchmark Results

Summary

duration

  • create10k: unsure 🔍 -2% - +1% (-14.18ms - +5.29ms)
    preact-local vs preact-main
  • filter-list: unsure 🔍 -1% - +0% (-0.13ms - +0.01ms)
    preact-local vs preact-main
  • hydrate1k: unsure 🔍 -2% - +2% (-1.17ms - +0.98ms)
    preact-local vs preact-main
  • many-updates: unsure 🔍 -1% - +1% (-0.21ms - +0.11ms)
    preact-local vs preact-main
  • replace1k: unsure 🔍 -1% - +2% (-0.49ms - +1.29ms)
    preact-local vs preact-main
  • text-update: faster ✔ 1% - 9% (0.03ms - 0.17ms)
    preact-local vs preact-main
  • todo: faster ✔ 0% - 4% (0.04ms - 1.10ms)
    preact-local vs preact-main
  • update10th1k: unsure 🔍 -4% - +2% (-1.40ms - +0.55ms)
    preact-local vs preact-main

usedJSHeapSize

  • create10k: unsure 🔍 -0% - +0% (-0.04ms - +0.00ms)
    preact-local vs preact-main
  • filter-list: unsure 🔍 -0% - +0% (-0.00ms - +0.00ms)
    preact-local vs preact-main
  • hydrate1k: unsure 🔍 -4% - +3% (-0.22ms - +0.15ms)
    preact-local vs preact-main
  • many-updates: unsure 🔍 -0% - -0% (-0.01ms - -0.00ms)
    preact-local vs preact-main
  • replace1k: unsure 🔍 -0% - +0% (-0.00ms - +0.00ms)
    preact-local vs preact-main
  • text-update: unsure 🔍 -3% - +2% (-0.03ms - +0.02ms)
    preact-local vs preact-main
  • todo: unsure 🔍 -0% - -0% (-0.00ms - -0.00ms)
    preact-local vs preact-main
  • update10th1k: faster ✔ 0% - 1% (0.00ms - 0.02ms)
    preact-local vs preact-main

Results

create10k

duration

VersionAvg timevs preact-localvs preact-main
preact-local832.61ms - 841.58ms-unsure 🔍
-2% - +1%
-14.18ms - +5.29ms
preact-main832.89ms - 850.18msunsure 🔍
-1% - +2%
-5.29ms - +14.18ms
-

usedJSHeapSize

VersionAvg timevs preact-localvs preact-main
preact-local19.22ms - 19.26ms-unsure 🔍
-0% - +0%
-0.04ms - +0.00ms
preact-main19.25ms - 19.27msunsure 🔍
-0% - +0%
-0.00ms - +0.04ms
-
filter-list

duration

VersionAvg timevs preact-localvs preact-main
preact-local16.42ms - 16.49ms-unsure 🔍
-1% - +0%
-0.13ms - +0.01ms
preact-main16.46ms - 16.58msunsure 🔍
-0% - +1%
-0.01ms - +0.13ms
-

usedJSHeapSize

VersionAvg timevs preact-localvs preact-main
preact-local1.57ms - 1.57ms-unsure 🔍
-0% - +0%
-0.00ms - +0.00ms
preact-main1.57ms - 1.57msunsure 🔍
-0% - +0%
-0.00ms - +0.00ms
-
hydrate1k

duration

VersionAvg timevs preact-localvs preact-main
preact-local57.85ms - 59.18ms-unsure 🔍
-2% - +2%
-1.17ms - +0.98ms
preact-main57.76ms - 59.45msunsure 🔍
-2% - +2%
-0.98ms - +1.17ms
-

usedJSHeapSize

VersionAvg timevs preact-localvs preact-main
preact-local4.99ms - 5.19ms-unsure 🔍
-4% - +3%
-0.22ms - +0.15ms
preact-main4.97ms - 5.28msunsure 🔍
-3% - +4%
-0.15ms - +0.22ms
-
many-updates

duration

VersionAvg timevs preact-localvs preact-main
preact-local16.32ms - 16.57ms-unsure 🔍
-1% - +1%
-0.21ms - +0.11ms
preact-main16.39ms - 16.59msunsure 🔍
-1% - +1%
-0.11ms - +0.21ms
-

usedJSHeapSize

VersionAvg timevs preact-localvs preact-main
preact-local3.75ms - 3.75ms-unsure 🔍
-0% - -0%
-0.01ms - -0.00ms
preact-main3.75ms - 3.75msunsure 🔍
+0% - +0%
+0.00ms - +0.01ms
-
replace1k
  • Browser: chrome-headless
  • Sample size: 100
  • Built by: CI #5737
  • Commit: 8325d96

duration

VersionAvg timevs preact-localvs preact-main
preact-local55.37ms - 56.69ms-unsure 🔍
-1% - +2%
-0.49ms - +1.29ms
preact-main55.04ms - 56.23msunsure 🔍
-2% - +1%
-1.29ms - +0.49ms
-

usedJSHeapSize

VersionAvg timevs preact-localvs preact-main
preact-local3.02ms - 3.03ms-unsure 🔍
-0% - +0%
-0.00ms - +0.00ms
preact-main3.02ms - 3.03msunsure 🔍
-0% - +0%
-0.00ms - +0.00ms
-

run-warmup-0

VersionAvg timevs preact-localvs preact-main
preact-local27.24ms - 28.11ms-unsure 🔍
-1% - +3%
-0.16ms - +0.94ms
preact-main26.94ms - 27.62msunsure 🔍
-3% - +1%
-0.94ms - +0.16ms
-

run-warmup-1

VersionAvg timevs preact-localvs preact-main
preact-local32.95ms - 34.40ms-unsure 🔍
-3% - +3%
-1.05ms - +0.99ms
preact-main33.00ms - 34.42msunsure 🔍
-3% - +3%
-0.99ms - +1.05ms
-

run-warmup-2

VersionAvg timevs preact-localvs preact-main
preact-local29.83ms - 31.05ms-unsure 🔍
-3% - +3%
-0.96ms - +0.82ms
preact-main29.85ms - 31.16msunsure 🔍
-3% - +3%
-0.82ms - +0.96ms
-

run-warmup-3

VersionAvg timevs preact-localvs preact-main
preact-local25.82ms - 26.17ms-unsure 🔍
-2% - +0%
-0.45ms - +0.07ms
preact-main26.00ms - 26.37msunsure 🔍
-0% - +2%
-0.07ms - +0.45ms
-

run-warmup-4

VersionAvg timevs preact-localvs preact-main
preact-local23.41ms - 24.93ms-unsure 🔍
-5% - +4%
-1.09ms - +1.04ms
preact-main23.44ms - 24.95msunsure 🔍
-4% - +5%
-1.04ms - +1.09ms
-

run-final

VersionAvg timevs preact-localvs preact-main
preact-local20.88ms - 21.41ms-unsure 🔍
-1% - +2%
-0.14ms - +0.49ms
preact-main20.80ms - 21.13msunsure 🔍
-2% - +1%
-0.49ms - +0.14ms
-
text-update
  • Browser: chrome-headless
  • Sample size: 220
  • Built by: CI #5737
  • Commit: 8325d96

duration

VersionAvg timevs preact-localvs preact-main
preact-local1.73ms - 1.83ms-faster ✔
1% - 9%
0.03ms - 0.17ms
preact-main1.83ms - 1.93msslower ❌
1% - 10%
0.03ms - 0.17ms
-

usedJSHeapSize

VersionAvg timevs preact-localvs preact-main
preact-local1.02ms - 1.05ms-unsure 🔍
-3% - +2%
-0.03ms - +0.02ms
preact-main1.02ms - 1.05msunsure 🔍
-2% - +3%
-0.02ms - +0.03ms
-
todo

duration

VersionAvg timevs preact-localvs preact-main
preact-local30.02ms - 30.45ms-faster ✔
0% - 4%
0.04ms - 1.10ms
preact-main30.32ms - 31.29msslower ❌
0% - 4%
0.04ms - 1.10ms
-

usedJSHeapSize

VersionAvg timevs preact-localvs preact-main
preact-local1.27ms - 1.27ms-unsure 🔍
-0% - -0%
-0.00ms - -0.00ms
preact-main1.28ms - 1.28msunsure 🔍
+0% - +0%
+0.00ms - +0.00ms
-
update10th1k

duration

VersionAvg timevs preact-localvs preact-main
preact-local32.56ms - 33.89ms-unsure 🔍
-4% - +2%
-1.40ms - +0.55ms
preact-main32.94ms - 34.36msunsure 🔍
-2% - +4%
-0.55ms - +1.40ms
-

usedJSHeapSize

VersionAvg timevs preact-localvs preact-main
preact-local2.97ms - 2.98ms-faster ✔
0% - 1%
0.00ms - 0.02ms
preact-main2.98ms - 2.99msslower ❌
0% - 1%
0.00ms - 0.02ms
-

tachometer-reporter-action v2 for CI

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown

Size Change: -21 B (-0.13%)

Total Size: 15.7 kB

📦 View Changed
Filename Size Change
dist/preact.mjs 4.89 kB -21 B (-0.43%)
ℹ️ View Unchanged
Filename Size
compat/dist/compat.mjs 3.71 kB
debug/dist/debug.mjs 3.89 kB
devtools/dist/devtools.mjs 274 B
hooks/dist/hooks.mjs 1.55 kB
jsx-runtime/dist/jsxRuntime.mjs 861 B
test-utils/dist/testUtils.mjs 473 B

compressed-size-action

diff() already returns the next host cursor for function components;
elements returned their own DOM node, which diffChildren discarded and
re-derived via newDom.nextSibling. Return the sibling directly so the
function-type check in the hot loop can go. The JSON-injection guard
now returns UNDEFINED so an invalid child leaves the cursor untouched
instead of clobbering it with NULL.
@coveralls

coveralls commented Jul 29, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 98.534%. remained the same — perf/v11-retained-subtree-bailout into main

@JoviDeCroock
JoviDeCroock force-pushed the perf/v11-retained-subtree-bailout branch from 9c0103f to 8325d96 Compare July 29, 2026 17:02
@JoviDeCroock
JoviDeCroock merged commit 642aed0 into main Jul 30, 2026
13 checks passed
@JoviDeCroock
JoviDeCroock deleted the perf/v11-retained-subtree-bailout branch July 30, 2026 08:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants