facebook / react

The library for web and native user interfaces.
https://react.dev
MIT License
228.55k stars 46.78k forks source link

propagateContextChange visits all child fibers #20661

Closed intellild closed 6 months ago

intellild commented 3 years ago

propagateContextChange in the current context implementation visits all child fibers rather than fibers that have dependency on the context. In my case, there may be thousands of child components and 99% of them don't have dependency on the context, which causes propagateContextChange spends a lot of scripting time but eventually does 'nothing'.

gaearon commented 3 years ago

It visits all fibers because that's its purpose — to find the ones that depend on context. There are some ways we're planning to optimize the algorithm but they'll need significant refactors so don't count on them happening soon. For now, it's essentially bruteforce by design.

However, it is possible that there might be some ways to optimize the function itself. It could very well be possible that it has become slower since the initial implementation due to some accidental deopt or compiler behaivor. So if you want to make a synthetic benchmark to stress-test it, and look for optimization opportunities, that would be appreciated.

intellild commented 3 years ago

@gaearon I can have a try. There are two versions of files in react-resonciler(old.js and new.js). Which should I modify?

gaearon commented 3 years ago

Not sure which ones you'll get on build.. Try changing both and see which gets used?

intellild commented 3 years ago

I opened a draft pull request #20786 Improvements are significant. I use https://github.com/intellild/react-context-performance for profiling. This implementation uses a 32bit number as a bitset. If there are more than 32 contexts, it falls back to the current logic. Choices are:

before

屏幕截图 2021-02-10 211229

after

屏幕截图 2021-02-10 211602

intellild commented 3 years ago

I noticed that the bubbleProperties spent a lot of time on my branch. It seems that I missed something related to bailouts.

gaearon commented 3 years ago

Thanks for the PR! I don't think this approach would work for us because then you have a performance cliff as soon as you have more than 32 contexts. We generally prefer predictable performance over "fast in some cases", because otherwise seemingly small changes can have disproportionate impact on the whole application, and it becomes very difficult to debug perf problems.

intellild commented 3 years ago

After some rethinking, it seems a number array is good enough, since there won't be large number of contexts. I will have a try later

gaearon commented 3 years ago

since there won't be large number of contexts

This is not correct; we should expect that a large app (which is where this optimization is most needed) will have a large number of contexts.

gaearon commented 3 years ago

By the way, originally I referred to optimizing in terms of optimizing the brute force traversal itself. It's quite likely that there might be some engine deopts there, e.g. due to how GCC compiles code. So it would be good to look into the compile output and if there are ways to optimize it.

intellild commented 3 years ago

large number of contexts

I mean thousands of contexts. One number supports 32 contexts and a number array with length 32 supports 32 * 32 contexts. That's 1024 contexts and 32 is only one tree node size of a hash tree. Thus I believe this is quite enough to optimize the scenario and a hash tree is not necessary.

intellild commented 3 years ago

I checked the output of production build and closure compiler did a great job.

I tried the array method and it works fine although the implementation is not correct. Will this get merged ? I will finish it if it would get merged

markerikson commented 3 years ago

@intellild my guess is probably not, given that Andrew is already working on https://github.com/facebook/react/pull/20646 and https://github.com/facebook/react/pull/20890 .

intellild commented 3 years ago

@markerikson Thanks. I read the PR and it seems they are not conflicting

markerikson commented 3 years ago

Hmm. Well, if it doesn't conflict there might be a chance, but that'd really be an @acdlite kind of question.

windmaomao commented 3 years ago

Just out of curiosity, i wonder how this finding child fiber can be optimized. A fiber can be very deep deep down the branch, the official version traces from the origin of the context to each child, because each fiber has a dependencies to hold all contexts.

If we go the opposite way, suppose we store all fibers referencing the context in the context fiber's contextChilds. In readContext, we do this

function useContext(context) {
  // go find the context fiber in currentlyRenderingFiber's ancestors 
  // and then add the currentlyRenderingFiber into `contextChilds`
  markUpdateContextFiber(context, currentlyRenderingFiber)

  return context._currentValue
}

Then maybe this will save work in propagateContextChange. Of course i don't know how this happens to the nested Providers, maybe just going up till it hits the first context that matches. Anyway just an idea.

Questions: just a note here, seems Context.Provider can be pretty heavy especially in a situation we have a very nested fiber tree, and it happens we have lots of usages of readContext. In that case, is renderLanes the saver to throttle the number of re-render? Because otherwise it might be worth than a hard refresh of the page.

intellild commented 3 years ago

If we go the opposite way, suppose we store all fibers referencing the context in the context fiber's contextChilds. In readContext, we do this

@windmaomao I'm afraid there are some concurrent issues that are very hard to resolve

stale[bot] commented 2 years ago

This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!

github-actions[bot] commented 6 months ago

Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you!