Skip to content

Avoid traversing retained subtrees - #5182

Merged
JoviDeCroock merged 2 commits into
v10.xfrom
perf/v10-retained-subtree-bailout
Jul 30, 2026
Merged

Avoid traversing retained subtrees#5182
JoviDeCroock merged 2 commits into
v10.xfrom
perf/v10-retained-subtree-bailout

Conversation

@JoviDeCroock

@JoviDeCroock JoviDeCroock commented Jul 29, 2026

Copy link
Copy Markdown
Member

Summary

Use getDomSibling() after successful shouldComponentUpdate/memo bailouts instead of recursively walking retained child trees through insert(). Actual placements still use insert().

Use getDomSibling to recover the position after a retained subtree instead of recursively walking it through insert(). Keep leaf VNodes on the direct nextSibling path and reserve insert() for actual placements.

PR #5172 independently improves keyed reordering on v10.x and is not included in this branch.
@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown

📊 Tachometer Benchmark Results

Summary

duration

  • create10k: unsure 🔍 -2% - +2% (-20.48ms - +20.37ms)
    preact-local vs preact-main
  • filter-list: unsure 🔍 -0% - +1% (-0.03ms - +0.21ms)
    preact-local vs preact-main
  • hydrate1k: unsure 🔍 -2% - +4% (-0.62ms - +1.41ms)
    preact-local vs preact-main
  • many-updates: unsure 🔍 -1% - +1% (-0.20ms - +0.14ms)
    preact-local vs preact-main
  • replace1k: unsure 🔍 -1% - +3% (-0.50ms - +1.66ms)
    preact-local vs preact-main
  • text-update: unsure 🔍 -6% - +1% (-0.10ms - +0.02ms)
    preact-local vs preact-main
  • todo: unsure 🔍 -2% - +1% (-0.54ms - +0.46ms)
    preact-local vs preact-main
  • update10th1k: unsure 🔍 -4% - +5% (-1.05ms - +1.13ms)
    preact-local vs preact-main

usedJSHeapSize

  • create10k: unsure 🔍 -0% - +0% (-0.02ms - +0.02ms)
    preact-local vs preact-main
  • filter-list: unsure 🔍 -0% - +0% (-0.00ms - +0.00ms)
    preact-local vs preact-main
  • hydrate1k: unsure 🔍 -2% - +5% (-0.11ms - +0.25ms)
    preact-local vs preact-main
  • many-updates: unsure 🔍 -0% - -0% (-0.01ms - -0.00ms)
    preact-local vs preact-main
  • replace1k: unsure 🔍 -0% - -0% (-0.01ms - -0.00ms)
    preact-local vs preact-main
  • text-update: unsure 🔍 -5% - -0% (-0.06ms - +0.00ms)
    preact-local vs preact-main
  • todo: unsure 🔍 -0% - -0% (-0.00ms - -0.00ms)
    preact-local vs preact-main
  • update10th1k: unsure 🔍 -0% - +0% (-0.01ms - +0.00ms)
    preact-local vs preact-main

Results

create10k

duration

VersionAvg timevs preact-localvs preact-main
preact-local884.38ms - 906.47ms-unsure 🔍
-2% - +2%
-20.48ms - +20.37ms
preact-main878.30ms - 912.66msunsure 🔍
-2% - +2%
-20.37ms - +20.48ms
-

usedJSHeapSize

VersionAvg timevs preact-localvs preact-main
preact-local19.35ms - 19.38ms-unsure 🔍
-0% - +0%
-0.02ms - +0.02ms
preact-main19.35ms - 19.38msunsure 🔍
-0% - +0%
-0.02ms - +0.02ms
-
filter-list

duration

VersionAvg timevs preact-localvs preact-main
preact-local16.54ms - 16.78ms-unsure 🔍
-0% - +1%
-0.03ms - +0.21ms
preact-main16.55ms - 16.60msunsure 🔍
-1% - +0%
-0.21ms - +0.03ms
-

usedJSHeapSize

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

duration

VersionAvg timevs preact-localvs preact-main
preact-local37.97ms - 39.63ms-unsure 🔍
-2% - +4%
-0.62ms - +1.41ms
preact-main37.81ms - 38.99msunsure 🔍
-4% - +2%
-1.41ms - +0.62ms
-

usedJSHeapSize

VersionAvg timevs preact-localvs preact-main
preact-local4.96ms - 5.28ms-unsure 🔍
-2% - +5%
-0.11ms - +0.25ms
preact-main4.98ms - 5.13msunsure 🔍
-5% - +2%
-0.25ms - +0.11ms
-
many-updates

duration

VersionAvg timevs preact-localvs preact-main
preact-local16.31ms - 16.54ms-unsure 🔍
-1% - +1%
-0.20ms - +0.14ms
preact-main16.32ms - 16.58msunsure 🔍
-1% - +1%
-0.14ms - +0.20ms
-

usedJSHeapSize

VersionAvg timevs preact-localvs preact-main
preact-local3.73ms - 3.73ms-unsure 🔍
-0% - -0%
-0.01ms - -0.00ms
preact-main3.73ms - 3.73msunsure 🔍
+0% - +0%
+0.00ms - +0.01ms
-
replace1k
  • Browser: chrome-headless
  • Sample size: 100
  • Built by: CI #5734
  • Commit: 8623721

duration

VersionAvg timevs preact-localvs preact-main
preact-local58.03ms - 59.84ms-unsure 🔍
-1% - +3%
-0.50ms - +1.66ms
preact-main57.76ms - 58.95msunsure 🔍
-3% - +1%
-1.66ms - +0.50ms
-

usedJSHeapSize

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

run-warmup-0

VersionAvg timevs preact-localvs preact-main
preact-local28.05ms - 29.04ms-unsure 🔍
-2% - +3%
-0.48ms - +0.83ms
preact-main27.93ms - 28.80msunsure 🔍
-3% - +2%
-0.83ms - +0.48ms
-

run-warmup-1

VersionAvg timevs preact-localvs preact-main
preact-local32.69ms - 33.79ms-unsure 🔍
-3% - +2%
-0.97ms - +0.51ms
preact-main32.97ms - 33.97msunsure 🔍
-2% - +3%
-0.51ms - +0.97ms
-

run-warmup-2

VersionAvg timevs preact-localvs preact-main
preact-local33.30ms - 34.87ms-unsure 🔍
-2% - +4%
-0.65ms - +1.33ms
preact-main33.14ms - 34.35msunsure 🔍
-4% - +2%
-1.33ms - +0.65ms
-

run-warmup-3

VersionAvg timevs preact-localvs preact-main
preact-local26.72ms - 27.80ms-unsure 🔍
-2% - +3%
-0.51ms - +0.72ms
preact-main26.87ms - 27.44msunsure 🔍
-3% - +2%
-0.72ms - +0.51ms
-

run-warmup-4

VersionAvg timevs preact-localvs preact-main
preact-local23.39ms - 24.52ms-unsure 🔍
-3% - +3%
-0.81ms - +0.76ms
preact-main23.44ms - 24.52msunsure 🔍
-3% - +3%
-0.76ms - +0.81ms
-

run-final

VersionAvg timevs preact-localvs preact-main
preact-local21.64ms - 22.35ms-unsure 🔍
-1% - +3%
-0.14ms - +0.69ms
preact-main21.51ms - 21.94msunsure 🔍
-3% - +1%
-0.69ms - +0.14ms
-
text-update
  • Browser: chrome-headless
  • Sample size: 220
  • Built by: CI #5734
  • Commit: 8623721

duration

VersionAvg timevs preact-localvs preact-main
preact-local1.70ms - 1.79ms-unsure 🔍
-6% - +1%
-0.10ms - +0.02ms
preact-main1.74ms - 1.84msunsure 🔍
-1% - +6%
-0.02ms - +0.10ms
-

usedJSHeapSize

VersionAvg timevs preact-localvs preact-main
preact-local0.99ms - 1.03ms-unsure 🔍
-5% - -0%
-0.06ms - +0.00ms
preact-main1.01ms - 1.06msunsure 🔍
-0% - +6%
-0.00ms - +0.06ms
-
todo

duration

VersionAvg timevs preact-localvs preact-main
preact-local34.07ms - 34.65ms-unsure 🔍
-2% - +1%
-0.54ms - +0.46ms
preact-main33.99ms - 34.82msunsure 🔍
-1% - +2%
-0.46ms - +0.54ms
-

usedJSHeapSize

VersionAvg timevs preact-localvs preact-main
preact-local1.26ms - 1.26ms-unsure 🔍
-0% - -0%
-0.00ms - -0.00ms
preact-main1.26ms - 1.26msunsure 🔍
+0% - +0%
+0.00ms - +0.00ms
-
update10th1k

duration

VersionAvg timevs preact-localvs preact-main
preact-local22.73ms - 24.24ms-unsure 🔍
-4% - +5%
-1.05ms - +1.13ms
preact-main22.66ms - 24.23msunsure 🔍
-5% - +4%
-1.13ms - +1.05ms
-

usedJSHeapSize

VersionAvg timevs preact-localvs preact-main
preact-local2.98ms - 2.99ms-unsure 🔍
-0% - +0%
-0.01ms - +0.00ms
preact-main2.98ms - 2.99msunsure 🔍
-0% - +0%
-0.00ms - +0.01ms
-

tachometer-reporter-action v2 for CI

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown

Size Change: -86 B (-0.11%)

Total Size: 79.4 kB

📦 View Changed
Filename Size Change
dist/preact.js 4.81 kB -14 B (-0.29%)
dist/preact.min.js 4.83 kB -10 B (-0.21%)
dist/preact.min.module.js 4.84 kB -13 B (-0.27%)
dist/preact.min.umd.js 4.85 kB -10 B (-0.21%)
dist/preact.mjs 4.83 kB -14 B (-0.29%)
dist/preact.module.js 4.83 kB -14 B (-0.29%)
dist/preact.umd.js 4.85 kB -11 B (-0.23%)
ℹ️ View Unchanged
Filename Size
compat/dist/compat.js 4.19 kB
compat/dist/compat.mjs 4.11 kB
compat/dist/compat.module.js 4.11 kB
compat/dist/compat.umd.js 4.25 kB
debug/dist/debug.js 3.85 kB
debug/dist/debug.mjs 3.85 kB
debug/dist/debug.module.js 3.85 kB
debug/dist/debug.umd.js 3.93 kB
devtools/dist/devtools.js 260 B
devtools/dist/devtools.mjs 274 B
devtools/dist/devtools.module.js 274 B
devtools/dist/devtools.umd.js 346 B
hooks/dist/hooks.js 1.51 kB
hooks/dist/hooks.mjs 1.54 kB
hooks/dist/hooks.module.js 1.54 kB
hooks/dist/hooks.umd.js 1.58 kB
jsx-runtime/dist/jsxRuntime.js 1.01 kB
jsx-runtime/dist/jsxRuntime.mjs 985 B
jsx-runtime/dist/jsxRuntime.module.js 985 B
jsx-runtime/dist/jsxRuntime.umd.js 1.08 kB
test-utils/dist/testUtils.js 473 B
test-utils/dist/testUtils.mjs 477 B
test-utils/dist/testUtils.module.js 477 B
test-utils/dist/testUtils.umd.js 555 B

compressed-size-action

- Inline the now single-use shouldPlace flag
- Use insertBefore's return value to set oldDom in insert()
- Set oldDom via getDomSibling directly in diff()'s bailout block so
  the retained-children identity branch in diffChildren can be
  dropped; the existing result branch picks it up

-12 B gzip / -16 B brotli on preact.min.js
@JoviDeCroock
JoviDeCroock merged commit 84778ce into v10.x Jul 30, 2026
12 checks passed
@JoviDeCroock
JoviDeCroock deleted the perf/v10-retained-subtree-bailout branch July 30, 2026 08:11
jviide pushed a commit to jviide/preact that referenced this pull request Jul 30, 2026
Port of preactjs#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
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.

2 participants