Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework children diffing to run in multiple phases #4180

Merged
merged 15 commits into from
Nov 7, 2023

Conversation

andrewiggins
Copy link
Member

@andrewiggins andrewiggins commented Oct 30, 2023

Restart of #4162 on top of lastest main.

Rework children diffing to run in multiple phases:

  1. First, find matches for all new VNodes in the old VNode tree, and determine which VNodes moved
  2. Second, remove any unmatched VNodes in the old tree from the DOM
  3. Finally, diff, move, and insert all VNodes in the new tree

A couple additional changes to highlight

  • fd710b8 Add new _flags VNode field to capture some diffing state (whether an oldVNode was matched and whether to insert a newVNode)
  • b6a1072 Combine placeChild and reorderChildren into a single insert function & simplify insertion logic in diffChildren
  • cd0127f Simplify some _nextDom logic by always setting & clearing it
  • 6eef30b Fold _hydrating field into _flags field (+12 B)

Fixes #4156

@github-actions
Copy link

github-actions bot commented Oct 30, 2023

📊 Tachometer Benchmark Results

Summary

duration

  • 02_replace1k: unsure 🔍 -1% - +4% (-1.31ms - +4.29ms)
    preact-local vs preact-main
  • 03_update10th1k_x16: unsure 🔍 -4% - +4% (-1.33ms - +1.25ms)
    preact-local vs preact-main
  • 07_create10k: unsure 🔍 -0% - +3% (-4.68ms - +40.12ms)
    preact-local vs preact-main
  • filter_list: unsure 🔍 -1% - +4% (-0.14ms - +0.67ms)
    preact-local vs preact-main
  • hydrate1k: faster ✔ 2% - 4% (1.54ms - 3.31ms)
    preact-local vs preact-main
  • many_updates: unsure 🔍 -4% - +5% (-0.95ms - +1.05ms)
    preact-local vs preact-main
  • text_update: slower ❌ 6% - 20% (0.28ms - 0.85ms)
    preact-local vs preact-main
  • todo: unsure 🔍 -0% - +5% (-0.01ms - +2.75ms)
    preact-local vs preact-main

usedJSHeapSize

  • 02_replace1k: unsure 🔍 +0% - +0% (+0.01ms - +0.02ms)
    preact-local vs preact-main
  • 03_update10th1k_x16: unsure 🔍 +0% - +0% (+0.00ms - +0.02ms)
    preact-local vs preact-main
  • 07_create10k: unsure 🔍 +0% - +0% (+0.01ms - +0.01ms)
    preact-local vs preact-main
  • filter_list: slower ❌ 1% - 2% (0.02ms - 0.03ms)
    preact-local vs preact-main
  • hydrate1k: unsure 🔍 +0% - +0% (+0.01ms - +0.03ms)
    preact-local vs preact-main
  • many_updates: slower ❌ 0% - 1% (0.01ms - 0.06ms)
    preact-local vs preact-main
  • text_update: unsure 🔍 -2% - +4% (-0.01ms - +0.03ms)
    preact-local vs preact-main
  • todo: slower ❌ 3% - 3% (0.02ms - 0.02ms)
    preact-local vs preact-main

Results

02_replace1k

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main117.56ms - 122.17ms-unsure 🔍
-4% - +1%
-4.29ms - +1.31ms
unsure 🔍
-1% - +3%
-1.47ms - +3.34ms
preact-local119.76ms - 122.95msunsure 🔍
-1% - +4%
-1.31ms - +4.29ms
-slower ❌
1% - 4%
0.69ms - 4.17ms
preact-hooks118.23ms - 119.62msunsure 🔍
-3% - +1%
-3.34ms - +1.47ms
faster ✔
1% - 3%
0.69ms - 4.17ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main3.31ms - 3.32ms-unsure 🔍
-0% - -0%
-0.02ms - -0.01ms
faster ✔
1% - 1%
0.03ms - 0.04ms
preact-local3.33ms - 3.33msunsure 🔍
+0% - +0%
+0.01ms - +0.02ms
-faster ✔
0% - 1%
0.02ms - 0.03ms
preact-hooks3.35ms - 3.36msslower ❌
1% - 1%
0.03ms - 0.04ms
slower ❌
0% - 1%
0.02ms - 0.03ms
-

run-warmup-0

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main47.47ms - 50.06ms-faster ✔
3% - 10%
1.46ms - 5.29ms
faster ✔
3% - 9%
1.43ms - 4.82ms
preact-local50.73ms - 53.56msslower ❌
3% - 11%
1.46ms - 5.29ms
-unsure 🔍
-3% - +4%
-1.53ms - +2.04ms
preact-hooks50.79ms - 52.98msslower ❌
3% - 10%
1.43ms - 4.82ms
unsure 🔍
-4% - +3%
-2.04ms - +1.53ms
-

run-warmup-1

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main56.35ms - 59.33ms-faster ✔
6% - 13%
4.04ms - 8.50ms
faster ✔
6% - 13%
4.04ms - 8.34ms
preact-local62.45ms - 65.77msslower ❌
7% - 15%
4.04ms - 8.50ms
-unsure 🔍
-3% - +4%
-2.19ms - +2.35ms
preact-hooks62.48ms - 65.58msslower ❌
7% - 15%
4.04ms - 8.34ms
unsure 🔍
-4% - +3%
-2.35ms - +2.19ms
-

run-warmup-2

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main47.28ms - 49.38ms-slower ❌
9% - 15%
3.84ms - 6.34ms
slower ❌
2% - 7%
0.88ms - 3.40ms
preact-local42.57ms - 43.91msfaster ✔
8% - 13%
3.84ms - 6.34ms
-faster ✔
4% - 8%
1.99ms - 3.91ms
preact-hooks45.50ms - 46.88msfaster ✔
2% - 7%
0.88ms - 3.40ms
slower ❌
5% - 9%
1.99ms - 3.91ms
-

run-warmup-3

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main39.94ms - 41.24ms-slower ❌
10% - 14%
3.74ms - 5.07ms
slower ❌
10% - 13%
3.48ms - 4.82ms
preact-local36.04ms - 36.33msfaster ✔
9% - 12%
3.74ms - 5.07ms
-faster ✔
0% - 1%
0.04ms - 0.47ms
preact-hooks36.27ms - 36.60msfaster ✔
9% - 12%
3.48ms - 4.82ms
slower ❌
0% - 1%
0.04ms - 0.47ms
-

run-warmup-4

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main57.14ms - 62.71ms-slower ❌
12% - 26%
6.04ms - 12.77ms
unsure 🔍
-2% - +9%
-0.92ms - +5.32ms
preact-local48.64ms - 52.40msfaster ✔
11% - 21%
6.04ms - 12.77ms
-faster ✔
9% - 16%
4.87ms - 9.55ms
preact-hooks56.33ms - 59.12msunsure 🔍
-9% - +1%
-5.32ms - +0.92ms
slower ❌
9% - 19%
4.87ms - 9.55ms
-

run-final

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main33.02ms - 36.58ms-unsure 🔍
-5% - +8%
-1.58ms - +2.67ms
unsure 🔍
-2% - +9%
-0.77ms - +2.87ms
preact-local33.09ms - 35.41msunsure 🔍
-8% - +4%
-2.67ms - +1.58ms
-unsure 🔍
-2% - +5%
-0.72ms - +1.72ms
preact-hooks33.37ms - 34.12msunsure 🔍
-8% - +2%
-2.87ms - +0.77ms
unsure 🔍
-5% - +2%
-1.72ms - +0.72ms
-
03_update10th1k_x16

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main34.38ms - 36.15ms-unsure 🔍
-4% - +4%
-1.25ms - +1.33ms
unsure 🔍
-5% - +2%
-1.87ms - +0.61ms
preact-local34.28ms - 36.16msunsure 🔍
-4% - +4%
-1.33ms - +1.25ms
-unsure 🔍
-5% - +2%
-1.95ms - +0.61ms
preact-hooks35.03ms - 36.77msunsure 🔍
-2% - +5%
-0.61ms - +1.87ms
unsure 🔍
-2% - +6%
-0.61ms - +1.95ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main3.27ms - 3.28ms-unsure 🔍
-0% - -0%
-0.02ms - -0.00ms
faster ✔
1% - 1%
0.03ms - 0.04ms
preact-local3.28ms - 3.29msunsure 🔍
+0% - +0%
+0.00ms - +0.02ms
-faster ✔
1% - 1%
0.02ms - 0.03ms
preact-hooks3.31ms - 3.31msslower ❌
1% - 1%
0.03ms - 0.04ms
slower ❌
1% - 1%
0.02ms - 0.03ms
-
07_create10k

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main1432.98ms - 1464.89ms-unsure 🔍
-3% - +0%
-40.12ms - +4.68ms
unsure 🔍
-3% - +1%
-39.25ms - +8.56ms
preact-local1450.94ms - 1482.37msunsure 🔍
-0% - +3%
-4.68ms - +40.12ms
-unsure 🔍
-1% - +2%
-21.37ms - +26.12ms
preact-hooks1446.48ms - 1482.08msunsure 🔍
-1% - +3%
-8.56ms - +39.25ms
unsure 🔍
-2% - +1%
-26.12ms - +21.37ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main26.32ms - 26.32ms-unsure 🔍
-0% - -0%
-0.01ms - -0.01ms
unsure 🔍
-0% - -0%
-0.04ms - -0.03ms
preact-local26.33ms - 26.33msunsure 🔍
+0% - +0%
+0.01ms - +0.01ms
-unsure 🔍
-0% - -0%
-0.02ms - -0.02ms
preact-hooks26.35ms - 26.35msunsure 🔍
+0% - +0%
+0.03ms - +0.04ms
unsure 🔍
+0% - +0%
+0.02ms - +0.02ms
-
filter_list

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main16.70ms - 16.87ms-unsure 🔍
-4% - +1%
-0.67ms - +0.14ms
unsure 🔍
-2% - +1%
-0.31ms - +0.11ms
preact-local16.66ms - 17.45msunsure 🔍
-1% - +4%
-0.14ms - +0.67ms
-unsure 🔍
-2% - +4%
-0.27ms - +0.60ms
preact-hooks16.70ms - 17.08msunsure 🔍
-1% - +2%
-0.11ms - +0.31ms
unsure 🔍
-4% - +2%
-0.60ms - +0.27ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main1.32ms - 1.33ms-faster ✔
1% - 2%
0.02ms - 0.03ms
faster ✔
3% - 4%
0.04ms - 0.05ms
preact-local1.35ms - 1.35msslower ❌
1% - 2%
0.02ms - 0.03ms
-faster ✔
1% - 2%
0.02ms - 0.02ms
preact-hooks1.37ms - 1.37msslower ❌
3% - 4%
0.04ms - 0.05ms
slower ❌
1% - 2%
0.02ms - 0.02ms
-
hydrate1k

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main85.94ms - 86.76ms-slower ❌
2% - 4%
1.54ms - 3.31ms
slower ❌
1% - 4%
1.26ms - 3.48ms
preact-local83.15ms - 84.71msfaster ✔
2% - 4%
1.54ms - 3.31ms
-unsure 🔍
-2% - +1%
-1.34ms - +1.24ms
preact-hooks82.95ms - 85.01msfaster ✔
1% - 4%
1.26ms - 3.48ms
unsure 🔍
-1% - +2%
-1.24ms - +1.34ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main6.10ms - 6.12ms-unsure 🔍
-0% - -0%
-0.03ms - -0.01ms
faster ✔
1% - 1%
0.03ms - 0.05ms
preact-local6.13ms - 6.13msunsure 🔍
+0% - +0%
+0.01ms - +0.03ms
-unsure 🔍
-0% - -0%
-0.03ms - -0.02ms
preact-hooks6.15ms - 6.16msslower ❌
1% - 1%
0.03ms - 0.05ms
unsure 🔍
+0% - +0%
+0.02ms - +0.03ms
-
many_updates

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main22.14ms - 23.79ms-unsure 🔍
-5% - +4%
-1.05ms - +0.95ms
unsure 🔍
-4% - +4%
-0.81ms - +0.87ms
preact-local22.45ms - 23.58msunsure 🔍
-4% - +5%
-0.95ms - +1.05ms
-unsure 🔍
-2% - +3%
-0.50ms - +0.66ms
preact-hooks22.78ms - 23.08msunsure 🔍
-4% - +4%
-0.87ms - +0.81ms
unsure 🔍
-3% - +2%
-0.66ms - +0.50ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main4.50ms - 4.54ms-faster ✔
0% - 1%
0.01ms - 0.06ms
faster ✔
0% - 1%
0.01ms - 0.06ms
preact-local4.54ms - 4.57msslower ❌
0% - 1%
0.01ms - 0.06ms
-unsure 🔍
-1% - +0%
-0.03ms - +0.02ms
preact-hooks4.54ms - 4.57msslower ❌
0% - 1%
0.01ms - 0.06ms
unsure 🔍
-0% - +1%
-0.02ms - +0.03ms
-
text_update

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main4.21ms - 4.60ms-faster ✔
6% - 17%
0.28ms - 0.85ms
faster ✔
9% - 20%
0.44ms - 1.04ms
preact-local4.76ms - 5.18msslower ❌
6% - 20%
0.28ms - 0.85ms
-unsure 🔍
-9% - +2%
-0.49ms - +0.13ms
preact-hooks4.92ms - 5.37msslower ❌
10% - 24%
0.44ms - 1.04ms
unsure 🔍
-3% - +10%
-0.13ms - +0.49ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main0.65ms - 0.68ms-unsure 🔍
-4% - +2%
-0.03ms - +0.01ms
faster ✔
2% - 7%
0.01ms - 0.05ms
preact-local0.66ms - 0.69msunsure 🔍
-2% - +4%
-0.01ms - +0.03ms
-faster ✔
1% - 7%
0.00ms - 0.05ms
preact-hooks0.68ms - 0.71msslower ❌
2% - 8%
0.01ms - 0.05ms
slower ❌
1% - 7%
0.00ms - 0.05ms
-
todo

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main50.19ms - 51.94ms-unsure 🔍
-5% - -0%
-2.75ms - +0.01ms
faster ✔
1% - 5%
0.28ms - 2.85ms
preact-local51.37ms - 53.51msunsure 🔍
-0% - +5%
-0.01ms - +2.75ms
-unsure 🔍
-3% - +2%
-1.61ms - +1.23ms
preact-hooks51.69ms - 53.57msslower ❌
1% - 6%
0.28ms - 2.85ms
unsure 🔍
-2% - +3%
-1.23ms - +1.61ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main0.78ms - 0.78ms-faster ✔
3% - 3%
0.02ms - 0.02ms
faster ✔
6% - 6%
0.05ms - 0.05ms
preact-local0.80ms - 0.80msslower ❌
3% - 3%
0.02ms - 0.02ms
-faster ✔
3% - 3%
0.02ms - 0.02ms
preact-hooks0.83ms - 0.83msslower ❌
6% - 6%
0.05ms - 0.05ms
slower ❌
3% - 3%
0.02ms - 0.02ms
-

tachometer-reporter-action v2 for Benchmarks

@coveralls
Copy link

coveralls commented Oct 30, 2023

Coverage Status

coverage: 99.465% (+0.001%) from 99.464%
when pulling 624151d on two-phase-diffChildren-2
into 99709ae on main.

@github-actions
Copy link

github-actions bot commented Oct 30, 2023

Size Change: +511 B (0%)

Total Size: 59.7 kB

Filename Size Change
compat/dist/compat.js 4 kB +27 B (0%)
compat/dist/compat.module.js 3.93 kB +33 B (0%)
compat/dist/compat.umd.js 4.08 kB +37 B (0%)
dist/preact.js 4.5 kB +77 B (1%)
dist/preact.min.js 4.52 kB +67 B (1%)
dist/preact.min.module.js 4.52 kB +70 B (1%)
dist/preact.min.umd.js 4.55 kB +67 B (1%)
dist/preact.module.js 4.51 kB +66 B (1%)
dist/preact.umd.js 4.55 kB +69 B (1%)
jsx-runtime/dist/jsxRuntime.umd.js 1.04 kB -2 B (0%)
ℹ️ View Unchanged
Filename Size Change
debug/dist/debug.js 3.52 kB 0 B
debug/dist/debug.module.js 3.52 kB 0 B
debug/dist/debug.umd.js 3.6 kB 0 B
devtools/dist/devtools.js 232 B 0 B
devtools/dist/devtools.module.js 240 B 0 B
devtools/dist/devtools.umd.js 315 B 0 B
hooks/dist/hooks.js 1.53 kB 0 B
hooks/dist/hooks.module.js 1.56 kB 0 B
hooks/dist/hooks.umd.js 1.62 kB 0 B
jsx-runtime/dist/jsxRuntime.js 963 B 0 B
jsx-runtime/dist/jsxRuntime.module.js 938 B 0 B
test-utils/dist/testUtils.js 453 B 0 B
test-utils/dist/testUtils.module.js 454 B 0 B
test-utils/dist/testUtils.umd.js 536 B 0 B

compressed-size-action

Copy link
Member

@JoviDeCroock JoviDeCroock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks amazing, I have a follow up with the test in #4181

@andrewiggins andrewiggins force-pushed the two-phase-diffChildren-2 branch 3 times, most recently from 447fbfa to 58001b4 Compare November 1, 2023 21:43
@andrewiggins andrewiggins force-pushed the two-phase-diffChildren-2 branch from 58001b4 to 5e5e387 Compare November 2, 2023 23:03
@andrewiggins
Copy link
Member Author

Okay, I think I've figured out the performance slowdown in many_updates. It appears we have hit a perf cliff with the number of properties on VNode.

I first tried some code reorganizations to see if it was a runtime difference (those experiments can be found on the two-phase-diffChildren-2-deopts branch). However none of them particularly moved the needle a lot. So I dug into the tracing analysis & glanced at a couple traces to see what was different.

To find the analysis tables shown below, go to the linked "Benchmark Debug" workflow run, view the logs for the "Bench many_updates" job, and expand the "Analyze logs if present" step. To view the trace timelines, download the many_updates_logs artifact of the linked workflow run and upload it to ui.perfetto.com. Reminder, tables shown below with the title "Count ..." have the wrong unit/label in the "Avg time" column. The column should be labelled "Avg count" and it shouldn't have a ms unit. Known issue in tachometer. Will maybe fix one day 😅

Below are some screenshots from tracing and analyzing a commit from two-phase-diffChildren-2-deopts branch (before a rebase and before playing with the number of VNode fields). You can see the results yourself here: https://github.com/preactjs/preact/actions/runs/6739103123.

image image image image image image

The first thing I noticed from examining a sample trace timeline of preact-local running the many_updates is that we have a lot of MinorGCs happening synchronously while V8 is running the benchmark. Looking at the timelines for preact-main, that almost never happens and MinorGCs almost always happen as a task after paint.

Looking at the analysis tables also confirms some other changes related to GC. Notice how while the "Count of MinorGCs" is basically the same between the "preact-main" and "preact-local" (this branch), the amount of time spent in MinorGC ("Sum of MinorGC") is much larger. Also, the amount of heap used after a MinorGC ("MinorGC usedHeapSizeAfter") is much larger! even though after a MajorGC the usedHeapSize is basically the same (couple hundred kilobytes different).

The biggest change to GC that I think this PR introduces is adding a field to VNode. So I tested again by removing the _hydrating field (since many_updates doesn't need it) and the benchmark appears to return to normal. That workflow run can be found here: https://github.com/preactjs/preact/actions/runs/6739355022/job/18320720488

image image image image

So I'm going to explore ways to perhaps reduce the number of VNode fields so fix this regression. I'm considering folding _hydrating into a _flags but removing a VNode field could be a breaking change, so would be curious to get other's thoughts here.

@JoviDeCroock
Copy link
Member

@andrewiggins

Removing internal only properties like _hydrating seem okay to me, as they are internal _hydrating. I don't know whether there are many others that we can remove without drastic changes to i.e. how we track DOM

@andrewiggins andrewiggins force-pushed the two-phase-diffChildren-2 branch from 08a277a to 6eef30b Compare November 4, 2023 19:58
@andrewiggins andrewiggins marked this pull request as ready for review November 5, 2023 03:43
Copy link
Member Author

@andrewiggins andrewiggins left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all the code golf suggestions <3

@JoviDeCroock
Copy link
Member

The text_update regression makes me wonder whether it would be worth having a specialized path but not sure 😅 can look deeper after we merge

Copy link
Member

@marvinhagemeister marvinhagemeister left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the move to flags as well. Agree with Jovi that we can figure out why that particular benchmark regressed later.

@andrewiggins andrewiggins merged commit 9aa4728 into main Nov 7, 2023
@andrewiggins andrewiggins deleted the two-phase-diffChildren-2 branch November 7, 2023 22:16
@JoviDeCroock JoviDeCroock mentioned this pull request Nov 10, 2023
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.

<iframe> reloads its contents after removing one of its previous siblings when used with react-slate
4 participants