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

Effects list refactor continued: passive effects traversal #19374

Merged
merged 11 commits into from Jul 29, 2020

Conversation

bvaughn
Copy link
Contributor

@bvaughn bvaughn commented Jul 15, 2020

  • Adds new Passive subtree tag value.
  • Bubbles passive flag to ancestors in the case of an unmount.
  • Adds recursive traversal for passive effects (mounts and unmounts).
  • Removes pendingPassiveHookEffectsMount and pendingPassiveHookEffectsUnmount arrays from work loop.
  • Re-adds sibling and child pointer detaching (temporarily removed in previous PR).
  • Addresses some minor TODO comments left over from previous PRs.

Co-authored-by: Luna Ruan luna@fb.com

@facebook-github-bot facebook-github-bot added CLA Signed React Core Team labels Jul 15, 2020
@codesandbox
Copy link

@codesandbox codesandbox bot commented Jul 15, 2020

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit ae37c7c:

Sandbox Source
React Configuration

@sizebot
Copy link

@sizebot sizebot commented Jul 15, 2020

Details of bundled changes.

Comparing: 0eea166...ae37c7c

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-dom.development.js 0.0% 0.0% 924.18 KB 924.27 KB 210.05 KB 210.1 KB NODE_DEV
ReactDOMForked-prod.js 🔺+0.6% 🔺+0.5% 397.32 KB 399.78 KB 73.79 KB 74.13 KB FB_WWW_PROD
react-dom.production.min.js 0.0% 0.0% 124.24 KB 124.24 KB 39.84 KB 39.84 KB NODE_PROD
ReactDOMForked-profiling.js +0.7% +0.6% 412.33 KB 415.28 KB 76.43 KB 76.87 KB FB_WWW_PROFILING
react-dom-server.node.production.min.js 0.0% 0.0% 20.72 KB 20.72 KB 7.68 KB 7.68 KB NODE_PROD
ReactDOMTesting-dev.js 0.0% 0.0% 959.55 KB 959.64 KB 215.13 KB 215.18 KB FB_WWW_DEV
react-dom-test-utils.production.min.js 0.0% 0.0% 12.71 KB 12.71 KB 4.97 KB 4.97 KB NODE_PROD
react-dom-unstable-fizz.browser.development.js 0.0% +0.1% 5.36 KB 5.36 KB 1.8 KB 1.8 KB UMD_DEV
react-dom-unstable-fizz.node.production.min.js 0.0% 🔺+0.2% 1.17 KB 1.17 KB 664 B 665 B NODE_PROD
react-dom-unstable-fizz.browser.production.min.js 0.0% 🔺+0.3% 1.2 KB 1.2 KB 703 B 705 B UMD_PROD
react-dom.development.js 0.0% 0.0% 970.76 KB 970.85 KB 212.61 KB 212.65 KB UMD_DEV
react-dom-unstable-fizz.browser.production.min.js 0.0% 🔺+0.3% 1.01 KB 1.01 KB 614 B 616 B NODE_PROD
react-dom.production.min.js 0.0% 0.0% 124.1 KB 124.1 KB 40.58 KB 40.58 KB UMD_PROD
react-dom.profiling.min.js 0.0% 0.0% 129.27 KB 129.27 KB 42.2 KB 42.21 KB UMD_PROFILING
ReactDOMForked-dev.js +0.5% +0.3% 997.74 KB 1002.37 KB 222.18 KB 222.93 KB FB_WWW_DEV
react-dom.profiling.min.js 0.0% 0.0% 129.62 KB 129.62 KB 41.46 KB 41.46 KB NODE_PROFILING
react-dom-server.browser.production.min.js 0.0% 0.0% 20.38 KB 20.38 KB 7.54 KB 7.54 KB UMD_PROD
ReactDOM-dev.js 0.0% 0.0% 994.4 KB 994.5 KB 221.92 KB 221.97 KB FB_WWW_DEV
ReactDOM-prod.js 0.0% 0.0% 395.86 KB 395.86 KB 73.5 KB 73.51 KB FB_WWW_PROD
react-dom-server.browser.development.js 0.0% 0.0% 137.6 KB 137.6 KB 36.47 KB 36.47 KB NODE_DEV
ReactDOM-profiling.js 0.0% 0.0% 410.26 KB 410.26 KB 76.13 KB 76.14 KB FB_WWW_PROFILING
react-dom-server.browser.production.min.js 0.0% 0.0% 20.3 KB 20.3 KB 7.52 KB 7.52 KB NODE_PROD
ReactDOMServer-dev.js 0.0% 0.0% 143.44 KB 143.44 KB 36.48 KB 36.48 KB FB_WWW_DEV
ReactDOMServer-prod.js 0.0% 0.0% 46.68 KB 46.68 KB 10.92 KB 10.93 KB FB_WWW_PROD
react-dom-test-utils.development.js 0.0% -0.0% 65.91 KB 65.91 KB 18.18 KB 18.18 KB UMD_DEV

Size changes (experimental)

Generated by 🚫 dangerJS against ae37c7c

@sizebot
Copy link

@sizebot sizebot commented Jul 15, 2020

Details of bundled changes.

Comparing: 0eea166...ae37c7c

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-dom.development.js 0.0% 0.0% 887.07 KB 887.16 KB 203.25 KB 203.29 KB NODE_DEV
ReactDOMForked-prod.js 🔺+0.6% 🔺+0.4% 408.45 KB 410.92 KB 75.44 KB 75.75 KB FB_WWW_PROD
react-dom.production.min.js 0.0% 0.0% 119.45 KB 119.45 KB 38.39 KB 38.39 KB NODE_PROD
ReactDOMForked-profiling.js +0.7% +0.5% 423.53 KB 426.48 KB 78.07 KB 78.45 KB FB_WWW_PROFILING
react-dom-server.node.production.min.js 0.0% 0.0% 20.26 KB 20.26 KB 7.6 KB 7.6 KB NODE_PROD
react-dom-test-utils.production.min.js 0.0% 0.0% 12.84 KB 12.84 KB 5.03 KB 5.03 KB UMD_PROD
ReactDOMTesting-dev.js 0.0% 0.0% 987.71 KB 987.81 KB 220.54 KB 220.58 KB FB_WWW_DEV
react-dom-test-utils.development.js 0.0% -0.0% 60.88 KB 60.88 KB 17.65 KB 17.65 KB NODE_DEV
react-dom.development.js 0.0% 0.0% 931.85 KB 931.94 KB 205.74 KB 205.79 KB UMD_DEV
react-dom.profiling.min.js 0.0% 0.0% 123.25 KB 123.25 KB 40.28 KB 40.28 KB UMD_PROFILING
ReactDOMForked-dev.js +0.5% +0.3% 1023.28 KB 1 MB 226.88 KB 227.63 KB FB_WWW_DEV
react-dom-server.browser.production.min.js 0.0% 0.0% 19.92 KB 19.92 KB 7.46 KB 7.46 KB UMD_PROD
ReactDOM-dev.js 0.0% 0.0% 1019.95 KB 1020.04 KB 226.57 KB 226.62 KB FB_WWW_DEV
ReactDOM-profiling.js 0.0% -0.0% 421.45 KB 421.45 KB 77.75 KB 77.75 KB FB_WWW_PROFILING
react-dom-server.browser.production.min.js 0.0% 0.0% 19.84 KB 19.84 KB 7.44 KB 7.44 KB NODE_PROD
ReactDOMServer-prod.js 0.0% 0.0% 47.54 KB 47.54 KB 11.14 KB 11.14 KB FB_WWW_PROD

ReactDOM: size: 0.0%, gzip: 0.0%

Size changes (stable)

Generated by 🚫 dangerJS against ae37c7c

@gaearon
Copy link
Member

@gaearon gaearon commented Jul 17, 2020

Is the delta link out of date? I see no changes.

@bvaughn
Copy link
Contributor Author

@bvaughn bvaughn commented Jul 17, 2020

@gaearon Whoops, the other two PRs have landed now 😄 You can just view it normally (although please keep in mind it's not ready for review, just a draft). I'll delete that line though.

@bvaughn bvaughn force-pushed the effects_list_refactor_passive_effects branch from 9b894fe to c536679 Compare Jul 22, 2020
@bvaughn bvaughn requested review from acdlite, sebmarkbage and gaearon Jul 22, 2020
@bvaughn bvaughn marked this pull request as ready for review Jul 22, 2020
@bvaughn bvaughn changed the title Effects list refactor continued: passive effects traversal [DRAFT] Effects list refactor continued: passive effects traversal Jul 22, 2020
@bvaughn
Copy link
Contributor Author

@bvaughn bvaughn commented Jul 22, 2020

Okay. This PR is ready for review now 😄

@bvaughn bvaughn force-pushed the effects_list_refactor_passive_effects branch from 352b88b to d96907c Compare Jul 22, 2020
@bvaughn bvaughn force-pushed the effects_list_refactor_passive_effects branch from d96907c to 9b91189 Compare Jul 24, 2020
@bvaughn bvaughn force-pushed the effects_list_refactor_passive_effects branch from 9b91189 to 157aa81 Compare Jul 27, 2020
@bvaughn bvaughn requested a review from acdlite Jul 27, 2020
@bvaughn bvaughn force-pushed the effects_list_refactor_passive_effects branch 3 times, most recently from 5b6bc9d to 15288ed Compare Jul 28, 2020
@bvaughn
Copy link
Contributor Author

@bvaughn bvaughn commented Jul 28, 2020

@acdlite @lunaruan I think 15288ed is the change we discussed earlier.

@bvaughn bvaughn requested a review from lunaruan Jul 28, 2020
// mark the parent so that we're sure to traverse after commit and run any unmount functions.
const primaryEffectTag = childToDelete.effectTag & PassiveMask;
const primarySubtreeTag = childToDelete.subtreeTag & PassiveSubtreeTag;
if (primaryEffectTag !== NoEffect || primarySubtreeTag !== NoSubtreeTag) {
Copy link
Member

@acdlite acdlite Jul 28, 2020

Choose a reason for hiding this comment

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

Maybe this optimization isn't worth it and we should always set the PassiveSubtreeTag on the parent when a child is deleted. i.e. add Deletion to PassiveMask.

Effects are very common — any large tree is almost certain to have at least one. So the optimization really only kicks in when you delete a leaf node. Not sure it's worth the extra code.

I'm almost tempted to say that PassiveStatic isn't worth it for the same reason; but since we're planning to have other static tags for things like portals, PassiveStatic doesn't add any net additional overhead, so might as well keep it.

What do you think?

Copy link
Contributor Author

@bvaughn bvaughn Jul 29, 2020

Choose a reason for hiding this comment

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

Maybe this optimization isn't worth it and we should always set the PassiveSubtreeTag on the parent when a child is deleted.

Given that the concept of static flags seems like a positive– and so keeping PassiveStatic around doesn't add any net overhead– the only question seems to be whether the few extra bytes required for us to check effectTag and subtreeTag are worth it, yeah?

With the current check, we might over traverse in the event that a child was deleted that had a passive effect, but no cleanup function. (Seems unlikely.) If we removed this check, we might over traverse any time a leaf node is deleted, period. Still, I guess that wouldn't be that bad since we'd only traverse the path to where the leaf was deleted.

TBH I think the cost is pretty small on either side (number or bytes or potential over traversing). Happy to remove the check for now.

cba61f7

Copy link
Member

@acdlite acdlite Jul 29, 2020

Choose a reason for hiding this comment

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

If we removed this check, we might over traverse any time a leaf node is deleted, period. Still, I guess that wouldn't be that bad since we'd only traverse the path to where the leaf was deleted.

Yeah I think that's fine. It's also related to the detachFiber discussion. Since we're moving most of that work to the passive phase, then we need to visit every deletion during the passive phase anyway.

Copy link
Contributor Author

@bvaughn bvaughn Jul 29, 2020

Choose a reason for hiding this comment

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

That's a good point I guess. I hadn't considered that aspect.

I consider the "detach" methods as kind of an optional optimization.

// Note that we can't clear child or sibling pointers yet,
// because they may be required for passive effects.
// These pointers will be cleared in a separate pass.
// Ideally, we should clear the child pointer of the parent alternate to let this
Copy link
Member

@acdlite acdlite Jul 28, 2020

Choose a reason for hiding this comment

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

If a Deletion always gets a passive effect scheduled on it, as suggested above, we could move this whole function to the passive phase. (Except maybe return and alternate, since those affect semantics, like if you call setState on an unmounted component.)

Copy link
Contributor Author

@bvaughn bvaughn Jul 29, 2020

Choose a reason for hiding this comment

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

If we don't clear return during layout, it would break a few things:

  • The unmounted state warning
  • How events bubble for the new API Dominic has been working on

That being said, it would fix a limitation of the Profiler API when it comes to measuring time spent in passive effect cleanup. That time currently doesn't get reported during an unmount (either a regular unmount or because of an error boundary) because the return pointer has been detached previously and so it can't bubble up to the nearest Profiler node. If we deferred detaching it until after passive, that time would be reported which is a plus.

Not sure if there would be a viable workaround for the event case, but for the unmounted state warning- we could walk the return path and look for Fibers with Deletion effects scheduled, except that this would have false positives for deletions that were scheduled and not committed so that's a bummer.

I guess we could always set another bit to mark a Fiber as having been deleted but at that point we aren't really saving much (aside from the Profiler fix).

Copy link
Contributor Author

@bvaughn bvaughn Jul 29, 2020

Choose a reason for hiding this comment

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

That being said, I believe we can safely defer everything except the return pointer cleanup to the passive effects phase which would be slightly better.

7d618ed

bvaughn added 3 commits Jul 29, 2020
* Adds new Passive subtree tag value.
* Bubbles passive flag to ancestors in the case of an unmount.
* Adds recursive traversal for passive effects (mounts and unmounts).
* Removes pendingPassiveHookEffectsMount and pendingPassiveHookEffectsUnmount arrays from work loop.
* Re-adds sibling and child pointer detaching (temporarily removed in previous PR).
* Addresses some minor TODO comments left over from previous PRs.
This allows certain concepts to persist without recalculting them, e.g. whether a subtree contains passive effects or portals. This helps for cases like nested unmounts that contain deep passive effects.
@bvaughn bvaughn force-pushed the effects_list_refactor_passive_effects branch from a807784 to 7d618ed Compare Jul 29, 2020
Instead, always schedule a passive traversal for a subtree containing a deleted node. The overhead of doing this (during the passive phase) is small, and effects are so common that a cleanup in the subtree is likely. This saves some bytes.
@bvaughn bvaughn requested a review from acdlite Jul 29, 2020
@acdlite
Copy link
Member

@acdlite acdlite commented Jul 29, 2020

Neat! This is an exciting PR

* Added Deletion effect to PassiveMask.
  * Removed the no longer needed explicit bubbling of Passive subtreeTag during deletion.
* Removed unnecessary deletion effectTag from fallback child.
* Detached alternate.return as well during layout.
* Removed PassiveStaticEffect from hook update.
* Removed unnecessary call to detachFiberAfterEffects.
@bvaughn
Copy link
Contributor Author

@bvaughn bvaughn commented Jul 29, 2020

Thanks for the feedback @acdlite! 😄

Made the suggested tweaks:

  • Added Deletion effect to PassiveMask.
    • Removed the no longer needed explicit bubbling of Passive subtreeTag during deletion.
  • Removed unnecessary Deletion effectTag from fallback child.
  • Detached alternate.return as well during layout.
  • Removed unnecessary PassiveStaticEffect from hook update (left on mount only).
  • Removed unnecessary call to detachFiberAfterEffects.

@bvaughn bvaughn requested a review from acdlite Jul 29, 2020
Copy link
Member

@acdlite acdlite left a comment

Just a few more things I noticed.

I think the only blocking one is my question about the new didBailout checks, and the rest can be deferred to a follow up.

primarySubtreeTag !== NoSubtreeTag ||
primaryEffectTag !== NoEffect
) {
flushPassiveUnmountEffects(fiberToDelete);
Copy link
Member

@acdlite acdlite Jul 29, 2020

Choose a reason for hiding this comment

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

Ah I didn't notice this was a recursive call.

I think this should be a different function, analagous to commitNestedUnmounts. They do slightly different things. This new function gets passed a fiber from the old tree, not the new tree. So for example, it doesn't have to check for nested deletions, because you're already inside a deleted tree.

(This might be related to the didBailout thing?)

Copy link
Contributor Author

@bvaughn bvaughn Jul 29, 2020

Choose a reason for hiding this comment

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

They don't really do different things. They traverse a tree and execute any passive effects that are scheduled.

This might be related to the didBailout thing?

Which thing?

Copy link
Member

@acdlite acdlite Jul 29, 2020

Choose a reason for hiding this comment

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

The key difference is that the top-most flushPassiveUnmountEffects gets passed a "previous" fiber, from the old tree, whereas the new function I'm proposing gets passed a "next" fiber, from the just-finished tree.

It goes back to the didBailout conversation from the last PR. The effectTag and subtreeTag on the "previous" tree represents a render that already happened. So we don't want to rely on those being correct for the current render, because that requires us to reset them in the commit phase. (However, it's fine to read the static part effectTag, and their corresponding accumulation in subtreeTag, since aren't specific to a particular render.)

Same with the deletions field. We shouldn't read previous.deletions because those deletions aren't part of the current commit phase; we already processed those. In other words, we shouldn't have to null out finishedWork.deletions once we're done processing them; I think it's only necessary right now because of the issue I'm describing.

Copy link
Contributor Author

@bvaughn bvaughn Jul 29, 2020

Choose a reason for hiding this comment

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

So for example, it doesn't have to check for nested deletions, because you're already inside a deleted tree.

This is a reasonable point. I can split the two functions apart, if you think the added bytes is better than the unnecessary null check.

In other words, we shouldn't have to null out finishedWork.deletions once we're done processing them

Clearing the deletions array was unnecessary. I'll remove that line.

Copy link
Contributor Author

@bvaughn bvaughn Jul 29, 2020

Choose a reason for hiding this comment

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

@acdlite Let me know if ae37c7c addresses your suggestion here?

@bvaughn bvaughn requested a review from acdlite Jul 29, 2020
@bvaughn
Copy link
Contributor Author

@bvaughn bvaughn commented Jul 29, 2020

There's been a lot of back and forth. I think I've made all of the suggested changes (that I can make). Let me know if I missed something!

Copy link
Member

@acdlite acdlite left a comment

Looks good! Thanks for the quick turnaround

@bvaughn
Copy link
Contributor Author

@bvaughn bvaughn commented Jul 29, 2020

No problem. Thanks for the fast feedback!

@bvaughn bvaughn merged commit eae90cd into facebook:master Jul 29, 2020
32 checks passed
@bvaughn bvaughn deleted the effects_list_refactor_passive_effects branch Jul 29, 2020
@gaearon gaearon mentioned this pull request Mar 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants