-
Notifications
You must be signed in to change notification settings - Fork 46.8k
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
[Fiber] Don't schedule class fibers without relevant lifecycles for commit #9105
Conversation
Seems like this removed a lot of overhead. That's a bit worrying since that indicates that we have a lot of overhead in the commit phase or in the perf measurement. We should probably make a comparison of running with and without perf instrumentation. |
Yea I'll definitely be doing that, it's on my list. |
With measurements on, the difference between before and after is 2ms vs 1ms. Is that enough to worry about? |
@gaearon Totally. Especially given you're probably perf testing on a top-end MacBook Pro. When I did perf testing with Inferno, I'd always run perf benchmarks again on an old crappy laptop I had, it always gave me a better indication on actual performance the end-users might get. Maybe we should get into the habit of doing something similar? |
Yeah, I just mean that I'd expect this to be ~1ms change on my example, so I don't think it's perf measurements exaggerating the issue. But I'll check if they are. |
Wouldn't it be interessting to test on a mobile device? What about RN? |
This is only relevant for Fiber codebase, it won't affect React apps using existing React version. It's also not that faster, just shaving off some minor unnecessary work in the algorithm we haven't optimized yet. |
The commit phase will often be running at the very end of a frame deadline. 1ms is 6% of the entire frame. Easily enough to push it over the edge to miss the frame. So yea, I actually think that 1ms matters a lot in this particular case. However, if that commit phase is invoking a bunch of If these updates are on native nodes for example, then it is much more likely to be an issue since we will have a lots of those updates in normal frame updates. |
@gaearon @sebmarkbage Just an idea: What if We could add |
@trueadm Yea, I think we should make a revamp of the life-cycles. It's filed under #7678. I called the new one It's harder to break in place because there's no incremental path to fixing things. |
if (current) { | ||
// During an update, we might either need to detach a ref | ||
// or to call the componentDidUpdate() hook in the commit phase. | ||
const currentRef = current.ref; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a separate effect for refs (Ref
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Ref effect gets marked here: https://github.com/facebook/react/blob/master/src/renderers/shared/fiber/ReactFiberBeginWork.js#L273-L274
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or wait, should ref change trigger a componentDidUpdate
call? I assumed no but maybe it should.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should not.
@sebmarkbage Thanks for the pointer on #7678. I'll comment there :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refs shouldn't cause an Update effect.
if (current) { | ||
// During an update, we might either need to detach a ref | ||
// or to call the componentDidUpdate() hook in the commit phase. | ||
const currentRef = current.ref; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Ref effect gets marked here: https://github.com/facebook/react/blob/master/src/renderers/shared/fiber/ReactFiberBeginWork.js#L273-L274
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was just looking at what |
I believe the reason this didn't break any tests is because by the time this code is fired, |
@gaearon Oh yup, that's a bug. It should check for a Ref effect, too. Which means that switch statement will get a bit more complicated :D |
Maybe we should just move that out to |
// Use Task priority for lifecycle updates | ||
if (nextEffect.effectTag & (Update | Callback)) { | ||
if (effectTag & (Update | Callback)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Outside the scope of this PR, but we should probably have a separate check for callbacks, for similar reasons.
if (!current && typeof instance.componentDidMount !== 'function') { | ||
return; | ||
} | ||
if (current && typeof instance.componentDidUpdate !== 'function') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style q: why are these separate checks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would you combine them? It feels a bit messy to me to have ||
and &&
with !
in a single condition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I can see how that looks a bit messy.
if ((!current && typeof instance.componentDidMount !== 'function') ||
(current && typeof instance.componentDidUpdate !== 'function')) {
return;
}
Alternatively, you could do
if (current) {
if (typeof instance.componentDidUpdate !== 'function') {
return;
}
} else {
if (typeof instance.componentDidMount !== 'function') {
return;
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried the second one earlier but I thought it's a bit less confusing when it exits if we collapse them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfwise is it the same? Does the engine have to check current
twice?
Also we should use current === null
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed
config.resetTextContent(nextEffect.stateNode); | ||
} | ||
|
||
if (effectTag & Ref) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should move this check into the switch statement. Already bad that ContentReset
isn't in there. It requires adding a bunch of combinations (e.g. PlacementAndRef
, PlacementAndUpdateAndRef
, UpdateAndRef
, etc) but it's better for perf.
Anything more that should be done here? Should I do the explosion of flags in this PR? |
Personally I'm ok with it being in a separate PR |
function markUpdateIfNecessary(workInProgress) { | ||
const current = workInProgress.alternate; | ||
const instance = workInProgress.stateNode; | ||
if (current !== null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everywhere you call this, you already have the instance in scope (probably a register) and know whether it is an update or a mount. I'd recommend just inlining this stuff. It will be less abstractions to read when you follow the code and easier to optimize because all these field reads and checks disappear.
2ac948b
to
1293e75
Compare
While working on #9071, I noticed that we're scheduling
Update
effect for all classes, regardless of whether they have something to do in the commit phase:(The
JSONArrow
class doesn't have lifecycle methods.)To fix this, I am checking whether there's work to do before marking a fiber with the
Update
tag. This mirrors the logic in the commit phase.To verify, I ran the same code with the new bundle, and now only class fibers with corresponding lifecycles are scheduled in the effect list:
This is a perf optimization and is unobservable from tests.