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

Manually track children's index & fix parent pointers when rerendering components #4170

Merged
merged 3 commits into from
Oct 26, 2023

Conversation

andrewiggins
Copy link
Member

@andrewiggins andrewiggins commented Oct 25, 2023

When rerendering a component we copy the component's VNode to make the oldVNode. However, copying the VNode breaks two connections in the virtual node tree:

  1. The _parent pointer of children of the oldVNode point to the original VNode, not the copied one. But in this situation, they should point to the oldVNode since they belong to the old tree and not the new tree we are about to rerender/reconstruct.

  2. the copied VNode (oldVNode) doesn't exist in its parent's children array, so in getDomSibling, the indexOf search we do on a VNode's parent won't work. So calls to getDomSibling(oldVNode) would not return the correct result.

Previously we discovered this issue when using null placeholders in components that setState. So the current fixes address that particular scenario by:

  1. Before calling getDomSibling, we set the child's parent to oldVNode (called oldParentVNode at this point in diffChildren). Before that fix, parent pointer of the child VNode would point to newParentVNode, which is incorrect. In diffChildren, we set newParentVNode._children = [] and fill it as we loop through new children, meaning child VNode would never occur in newParentVNode._children.

  2. We return _nextDom instead of _dom in getDomSibling if it is set. This fix coincidentally worked for the test cases we had because there was only one component with no sibling components. In this situation, getDomSibling would return a DOM node that is about to be unmounted and so all DOM after it would be re-appended. In the situation where a component has a sibling component that also returns a Fragment, returning _nextDom would prevent us from seeing this new sibling component and it's dom node and and so we would incorrectly append children after it's DOM. This situation is captured in a new test case.

Now, having a deeper understanding of the issue, this PR proposes two fixes to address this problems more holistically.

  1. To address the first issue, after copying the componet's VNode to oldVNode, we loop through the children and set their _parent pointer to oldVNode.

  2. Instead of using indexOf in getDomSibling we will track a VNode's index in it's parent's children array as a property on the VNode, removing the need for indexOf in getDomSibling. Note: this doesn't yet fully fix the second issue ("oldVNode doesn't exist in the parent's children array"), but it works around that issue by relying on the fact that in this situation the VNode it was copied from will be at the same index in the parent's children array and so works for getDomSibling. A future PR will attempt to address this problem more directly.

So in summary, tl;dr:

  1. After copying a component's VNode to rerender it, update the children to point to the copied VNode.
  2. Manually track a child's index in its parent array to remove the dependence on indexOf in getDomSibling.

Also note: #4162 also adds an _index property to VNodes so adding that property has multiple benefits, besides this one PR

@github-actions
Copy link

github-actions bot commented Oct 25, 2023

📊 Tachometer Benchmark Results

Summary

duration

  • 02_replace1k: faster ✔ 2% - 7% (2.35ms - 9.71ms)
    preact-local vs preact-main
  • 03_update10th1k_x16: unsure 🔍 -3% - +3% (-1.80ms - +1.77ms)
    preact-local vs preact-main
  • 07_create10k: unsure 🔍 -1% - +1% (-22.45ms - +19.39ms)
    preact-local vs preact-main
  • filter_list: unsure 🔍 -2% - +4% (-0.63ms - +1.11ms)
    preact-local vs preact-main
  • hydrate1k: unsure 🔍 -1% - +1% (-1.24ms - +1.31ms)
    preact-local vs preact-main
  • many_updates: unsure 🔍 -3% - +3% (-1.02ms - +1.09ms)
    preact-local vs preact-main
  • text_update: unsure 🔍 -7% - +6% (-0.37ms - +0.31ms)
    preact-local vs preact-main
  • todo: unsure 🔍 -3% - +2% (-1.67ms - +1.04ms)
    preact-local vs preact-main

usedJSHeapSize

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

Results

02_replace1k

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main131.05ms - 134.16ms-slower ❌
2% - 8%
2.35ms - 9.71ms
slower ❌
5% - 9%
6.63ms - 10.99ms
preact-local123.24ms - 129.92msfaster ✔
2% - 7%
2.35ms - 9.71ms
-unsure 🔍
-1% - +5%
-0.90ms - +6.45ms
preact-hooks122.26ms - 125.33msfaster ✔
5% - 8%
6.63ms - 10.99ms
unsure 🔍
-5% - +1%
-6.45ms - +0.90ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main3.27ms - 3.28ms-faster ✔
1% - 1%
0.03ms - 0.04ms
faster ✔
2% - 2%
0.05ms - 0.08ms
preact-local3.31ms - 3.32msslower ❌
1% - 1%
0.03ms - 0.04ms
-faster ✔
1% - 1%
0.02ms - 0.04ms
preact-hooks3.33ms - 3.35msslower ❌
2% - 2%
0.05ms - 0.08ms
slower ❌
1% - 1%
0.02ms - 0.04ms
-

run-warmup-0

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main48.13ms - 50.58ms-unsure 🔍
-5% - +3%
-2.55ms - +1.46ms
unsure 🔍
-6% - +1%
-3.06ms - +0.53ms
preact-local48.32ms - 51.48msunsure 🔍
-3% - +5%
-1.46ms - +2.55ms
-unsure 🔍
-5% - +3%
-2.78ms - +1.34ms
preact-hooks49.30ms - 51.94msunsure 🔍
-1% - +6%
-0.53ms - +3.06ms
unsure 🔍
-3% - +6%
-1.34ms - +2.78ms
-

run-warmup-1

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main60.18ms - 63.15ms-slower ❌
0% - 7%
0.30ms - 4.13ms
unsure 🔍
-4% - +3%
-2.72ms - +1.79ms
preact-local58.23ms - 60.66msfaster ✔
1% - 7%
0.30ms - 4.13ms
-faster ✔
1% - 8%
0.60ms - 4.76ms
preact-hooks60.43ms - 63.82msunsure 🔍
-3% - +4%
-1.79ms - +2.72ms
slower ❌
1% - 8%
0.60ms - 4.76ms
-

run-warmup-2

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main46.67ms - 48.28ms-faster ✔
5% - 10%
2.51ms - 4.96ms
faster ✔
4% - 9%
2.11ms - 4.62ms
preact-local50.29ms - 52.13msslower ❌
5% - 11%
2.51ms - 4.96ms
-unsure 🔍
-2% - +3%
-0.97ms - +1.71ms
preact-hooks49.87ms - 51.81msslower ❌
4% - 10%
2.11ms - 4.62ms
unsure 🔍
-3% - +2%
-1.71ms - +0.97ms
-

run-warmup-3

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main40.15ms - 41.07ms-faster ✔
2% - 5%
0.69ms - 2.18ms
faster ✔
2% - 6%
0.87ms - 2.46ms
preact-local41.46ms - 42.63msslower ❌
2% - 5%
0.69ms - 2.18ms
-unsure 🔍
-3% - +2%
-1.10ms - +0.64ms
preact-hooks41.63ms - 42.92msslower ❌
2% - 6%
0.87ms - 2.46ms
unsure 🔍
-2% - +3%
-0.64ms - +1.10ms
-

run-warmup-4

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main49.33ms - 50.42ms-faster ✔
21% - 27%
12.87ms - 18.63ms
faster ✔
22% - 29%
13.68ms - 19.85ms
preact-local62.80ms - 68.46msslower ❌
26% - 37%
12.87ms - 18.63ms
-unsure 🔍
-8% - +5%
-5.16ms - +3.14ms
preact-hooks63.61ms - 69.68msslower ❌
27% - 40%
13.68ms - 19.85ms
unsure 🔍
-5% - +8%
-3.14ms - +5.16ms
-

run-final

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main35.59ms - 37.70ms-unsure 🔍
-8% - +5%
-2.91ms - +2.02ms
unsure 🔍
-0% - +6%
-0.09ms - +2.16ms
preact-local34.86ms - 39.32msunsure 🔍
-6% - +8%
-2.02ms - +2.91ms
-unsure 🔍
-2% - +11%
-0.79ms - +3.74ms
preact-hooks35.22ms - 36.01msunsure 🔍
-6% - +0%
-2.16ms - +0.09ms
unsure 🔍
-10% - +2%
-3.74ms - +0.79ms
-
03_update10th1k_x16

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main51.67ms - 54.21ms-unsure 🔍
-3% - +3%
-1.77ms - +1.80ms
unsure 🔍
-5% - +2%
-2.63ms - +1.08ms
preact-local51.67ms - 54.18msunsure 🔍
-3% - +3%
-1.80ms - +1.77ms
-unsure 🔍
-5% - +2%
-2.63ms - +1.05ms
preact-hooks52.37ms - 55.07msunsure 🔍
-2% - +5%
-1.08ms - +2.63ms
unsure 🔍
-2% - +5%
-1.05ms - +2.63ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main3.22ms - 3.23ms-faster ✔
1% - 1%
0.04ms - 0.05ms
faster ✔
2% - 2%
0.06ms - 0.07ms
preact-local3.27ms - 3.27msslower ❌
1% - 2%
0.04ms - 0.05ms
-faster ✔
1% - 1%
0.02ms - 0.03ms
preact-hooks3.29ms - 3.30msslower ❌
2% - 2%
0.06ms - 0.07ms
slower ❌
1% - 1%
0.02ms - 0.03ms
-
07_create10k

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main1885.34ms - 1916.51ms-unsure 🔍
-1% - +1%
-19.39ms - +22.45ms
unsure 🔍
-1% - +1%
-23.47ms - +14.77ms
preact-local1885.44ms - 1913.35msunsure 🔍
-1% - +1%
-22.45ms - +19.39ms
-unsure 🔍
-1% - +1%
-23.69ms - +11.93ms
preact-hooks1894.20ms - 1916.35msunsure 🔍
-1% - +1%
-14.77ms - +23.47ms
unsure 🔍
-1% - +1%
-11.93ms - +23.69ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main25.88ms - 25.88ms-faster ✔
2% - 2%
0.44ms - 0.44ms
faster ✔
2% - 2%
0.46ms - 0.46ms
preact-local26.31ms - 26.32msslower ❌
2% - 2%
0.44ms - 0.44ms
-unsure 🔍
-0% - -0%
-0.02ms - -0.02ms
preact-hooks26.33ms - 26.34msslower ❌
2% - 2%
0.46ms - 0.46ms
unsure 🔍
+0% - +0%
+0.02ms - +0.02ms
-
filter_list

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main28.25ms - 29.36ms-unsure 🔍
-4% - +2%
-1.11ms - +0.63ms
unsure 🔍
-4% - +2%
-1.11ms - +0.53ms
preact-local28.38ms - 29.71msunsure 🔍
-2% - +4%
-0.63ms - +1.11ms
-unsure 🔍
-3% - +3%
-0.95ms - +0.84ms
preact-hooks28.50ms - 29.70msunsure 🔍
-2% - +4%
-0.53ms - +1.11ms
unsure 🔍
-3% - +3%
-0.84ms - +0.95ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main1.31ms - 1.34ms-unsure 🔍
-2% - +0%
-0.03ms - +0.00ms
faster ✔
2% - 5%
0.03ms - 0.06ms
preact-local1.33ms - 1.35msunsure 🔍
-0% - +2%
-0.00ms - +0.03ms
-faster ✔
1% - 4%
0.01ms - 0.05ms
preact-hooks1.36ms - 1.38msslower ❌
2% - 5%
0.03ms - 0.06ms
slower ❌
1% - 4%
0.01ms - 0.05ms
-
hydrate1k

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main121.51ms - 123.36ms-unsure 🔍
-1% - +1%
-1.31ms - +1.24ms
unsure 🔍
-1% - +1%
-1.22ms - +1.25ms
preact-local121.59ms - 123.34msunsure 🔍
-1% - +1%
-1.24ms - +1.31ms
-unsure 🔍
-1% - +1%
-1.15ms - +1.24ms
preact-hooks121.61ms - 123.23msunsure 🔍
-1% - +1%
-1.25ms - +1.22ms
unsure 🔍
-1% - +1%
-1.24ms - +1.15ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main6.01ms - 6.02ms-faster ✔
2% - 2%
0.09ms - 0.10ms
faster ✔
2% - 2%
0.12ms - 0.13ms
preact-local6.11ms - 6.11msslower ❌
2% - 2%
0.09ms - 0.10ms
-faster ✔
0% - 1%
0.02ms - 0.03ms
preact-hooks6.13ms - 6.14msslower ❌
2% - 2%
0.12ms - 0.13ms
slower ❌
0% - 1%
0.02ms - 0.03ms
-
many_updates

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main37.50ms - 39.12ms-unsure 🔍
-3% - +3%
-1.09ms - +1.02ms
faster ✔
1% - 8%
0.45ms - 3.04ms
preact-local37.67ms - 39.02msunsure 🔍
-3% - +3%
-1.02ms - +1.09ms
-faster ✔
1% - 7%
0.49ms - 2.93ms
preact-hooks39.05ms - 41.07msslower ❌
1% - 8%
0.45ms - 3.04ms
slower ❌
1% - 8%
0.49ms - 2.93ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main4.39ms - 4.42ms-faster ✔
2% - 3%
0.10ms - 0.14ms
faster ✔
2% - 3%
0.11ms - 0.15ms
preact-local4.51ms - 4.54msslower ❌
2% - 3%
0.10ms - 0.14ms
-unsure 🔍
-1% - +0%
-0.03ms - +0.02ms
preact-hooks4.51ms - 4.55msslower ❌
2% - 3%
0.11ms - 0.15ms
unsure 🔍
-0% - +1%
-0.02ms - +0.03ms
-
text_update

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main4.87ms - 5.35ms-unsure 🔍
-6% - +7%
-0.31ms - +0.37ms
faster ✔
1% - 13%
0.04ms - 0.72ms
preact-local4.84ms - 5.32msunsure 🔍
-7% - +6%
-0.37ms - +0.31ms
-faster ✔
1% - 13%
0.07ms - 0.75ms
preact-hooks5.25ms - 5.73msslower ❌
1% - 14%
0.04ms - 0.72ms
slower ❌
1% - 15%
0.07ms - 0.75ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main0.66ms - 0.68ms-unsure 🔍
-2% - +4%
-0.01ms - +0.03ms
unsure 🔍
-5% - +1%
-0.03ms - +0.00ms
preact-local0.65ms - 0.68msunsure 🔍
-4% - +2%
-0.03ms - +0.01ms
-faster ✔
0% - 6%
0.00ms - 0.04ms
preact-hooks0.67ms - 0.70msunsure 🔍
-1% - +5%
-0.00ms - +0.03ms
slower ❌
0% - 6%
0.00ms - 0.04ms
-
todo

duration

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main57.29ms - 59.10ms-unsure 🔍
-2% - +3%
-1.04ms - +1.67ms
unsure 🔍
-4% - +0%
-2.31ms - +0.15ms
preact-local56.88ms - 58.89msunsure 🔍
-3% - +2%
-1.67ms - +1.04ms
-faster ✔
0% - 5%
0.09ms - 2.70ms
preact-hooks58.45ms - 60.10msunsure 🔍
-0% - +4%
-0.15ms - +2.31ms
slower ❌
0% - 5%
0.09ms - 2.70ms
-

usedJSHeapSize

VersionAvg timevs preact-mainvs preact-localvs preact-hooks
preact-main0.78ms - 0.78ms-unsure 🔍
+0% - +0%
+0.00ms - +0.00ms
faster ✔
3% - 3%
0.02ms - 0.02ms
preact-local0.78ms - 0.78msunsure 🔍
-0% - -0%
-0.00ms - -0.00ms
-faster ✔
3% - 3%
0.02ms - 0.03ms
preact-hooks0.80ms - 0.80msslower ❌
3% - 3%
0.02ms - 0.02ms
slower ❌
3% - 3%
0.02ms - 0.03ms
-

tachometer-reporter-action v2 for Benchmarks

@coveralls
Copy link

coveralls commented Oct 25, 2023

Coverage Status

coverage: 99.288% (+0.005%) from 99.283% when pulling e6f0a01 on index-vnode-field into 8a51d1a on main.

@github-actions
Copy link

github-actions bot commented Oct 25, 2023

Size Change: -8 B (0%)

Total Size: 57.2 kB

Filename Size Change
dist/preact.js 4.42 kB +2 B (0%)
dist/preact.min.js 4.45 kB -3 B (0%)
dist/preact.min.module.js 4.45 kB -4 B (0%)
dist/preact.min.umd.js 4.47 kB -6 B (0%)
dist/preact.module.js 4.45 kB -5 B (0%)
dist/preact.umd.js 4.48 kB -6 B (0%)
jsx-runtime/dist/jsxRuntime.js 365 B +5 B (1%)
jsx-runtime/dist/jsxRuntime.module.js 331 B +5 B (1%)
jsx-runtime/dist/jsxRuntime.umd.js 445 B +4 B (0%)
ℹ️ View Unchanged
Filename Size Change
compat/dist/compat.js 3.95 kB 0 B
compat/dist/compat.module.js 3.87 kB 0 B
compat/dist/compat.umd.js 4.01 kB 0 B
debug/dist/debug.js 3.5 kB 0 B
debug/dist/debug.module.js 3.5 kB 0 B
debug/dist/debug.umd.js 3.58 kB 0 B
devtools/dist/devtools.js 231 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
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

In `getDomSibling` we do a `indexOf` search in the parent VNode to find the location of the vnode in the parent's children array. However, when rerendering a component we copy the VNode to make the oldVNode. This copy if the VNode means the `indexOf` search won't work cuz that copy doesn't exist the parent's children array. So calls to `getDomSibling(oldVNode)` would not return the correct result. This PR fixes this by manually tracking the index the childVNode is in the parent's children array so we no longer need to do the indexOf search.
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.

Damn, what a find! Thank you for the detailed explanation

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.

This is outstanding stuff! Love the detailed description of the issue. Now with an _index property on vnodes it makes me wonder if we can slowly transition to the internals in future PRs in the existing codebase. This is really good stuff 👍

@andrewiggins
Copy link
Member Author

Fun story for posterity, the initial push of this PR showed an unexpected regression in 02_replace1k benchmark and greater memory usage than expected (some rise is expected, but not what it initially showed). Upon looking into why, I realized I forgot to initialize the _index property in jsx/createElement, which fixed the regressions.

Just wanted to share a story where having these benchmarks led to finding and fixing issues :)

@andrewiggins andrewiggins merged commit cff2df5 into main Oct 26, 2023
13 checks passed
@andrewiggins andrewiggins deleted the index-vnode-field branch October 26, 2023 13:43
@JoviDeCroock JoviDeCroock mentioned this pull request Nov 3, 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.

None yet

4 participants