Closed intellild closed 6 months 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.
@gaearon I can have a try. There are two versions of files in react-resonciler(old.js and new.js). Which should I modify?
Not sure which ones you'll get on build.. Try changing both and see which gets used?
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:
propagateContextChange
. Instead, we can check contexts while reconciler walking down the tree.
I noticed that the bubbleProperties
spent a lot of time on my branch. It seems that I missed something related to bailouts.
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.
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
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.
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.
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.
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
@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 .
@markerikson Thanks. I read the PR and it seems they are not conflicting
Hmm. Well, if it doesn't conflict there might be a chance, but that'd really be an @acdlite kind of question.
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.
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
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!
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!
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 causespropagateContextChange
spends a lot of scripting time but eventually does 'nothing'.