-
Notifications
You must be signed in to change notification settings - Fork 222
[react-testing] improved performance by making descendants calculations lazy for element children #1812
Conversation
f990cdf
to
a9bbc71
Compare
53096fb
to
56b6bf3
Compare
ad264bd
to
0ee3586
Compare
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 code changes look good to me and the perf improvements are very exciting, we should definitely test this in web (or another big codebase) so we can be sure it doesn't secretly break anything though.
@@ -284,52 +286,40 @@ function defaultRender(element: React.ReactElement<unknown>) { | |||
return element; | |||
} | |||
|
|||
function flatten( | |||
element: Fiber, | |||
function fiberToElement( |
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.
+1 for rename, the preact version of the library I did similar renames :)
function getDescendants(element: any) { | ||
const descendants: Element<unknown>[] = []; | ||
// eslint-disable-next-line @typescript-eslint/prefer-for-of | ||
for (let i = 0; i < element.allChildren.length; i++) { |
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 could change our compilation settings for this package to have the foreach version of this not get transpiled, this is a node library, and node definitely supports foreach.
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've tried targeting ES2015
and changing that function to a more simplified:
function getDescendants(element: any) {
const descendants: Element<unknown>[] = [];
for (const child of element.allChildren) {
if (typeof child !== 'string') {
descendants.push(child, ...child.elementDescendants);
}
}
return descendants;
}
but it is unfortunately considerably slower :(
for of
with spread operator:
for loop with no spread:
(There's a slight variance between runs. I run these tests several times, wait for them to stabilize and take the lowest run time)
c848115
to
296b9b3
Compare
Found another optimization, was looking into why the depth is so large in web and noticed that our main Here's the latest run: Here are some flamegraphs of a single test highlighting the optimized functions before:after:Looks like any significant overhead from |
@melnikov-s HUGE! |
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.
@vsumner confirmed with a beta that the lazy evaluation change doesn't cause regressions in web, so that is good by me.
If you like you could pull the new er optimization into another PR and we can test that one with a beta too, and get the original one shipped ASAP
296b9b3
to
9b84566
Compare
Thanks, #1821 |
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.
@melnikov-s could you please add a changelog entry? And then feel free to merge and I can cut a new release.
9b84566
to
0674baa
Compare
Will do, is this a breaking change that requires a major version bump? I'm noticing that |
@melnikov-s I don't think its intended that folks manually construct elements, rather they can just use that said it is technically correct to do a major one I suppose |
0674baa
to
4172922
Compare
@TheMallen Yeah, I assumed it was for types. We might want to do |
Description
Fixes (issue #)
N/A
Type of change
I've been looking into a flaky slow test and doing some profiling and noticed that one of the significant bottlenecks is found in
react-testing
specifically when attempting to flatten a react fiber linked list into an array of elements. It seems like determining the descendants of each child is an exponential time algorithm. This has a pretty big impact on tests with big fiber trees. I've changed the descendants to be lazily evaluated and derived from the children though grabbing the root descendants still goes through the entire tree which is slow. Additionally I hand optimized some code, for example the spread operator when compiled with typescript to es5 into aspread
call seems to be a rather big culprit of performance issues in this computational heavy process.Performing the above optimization made a significant improvement to the test in question (which had a really big fiber tree ~80 in depth) :
Before:
After:
For other tests with smaller fiber trees the improvement is a more modest 10-20%.
Before:
After:
This should improve unit tests times across the board.
Note: A change in descendants order is a breaking one. I sketched out the order in this PR and it seems to be the same as before, but something worth verifying for the reviewers.
Checklist