-
Notifications
You must be signed in to change notification settings - Fork 47.2k
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
React 16.6.0 performance is worse than 16.5.2 #13987
Comments
Thanks for help. Here're files: It is |
If your code is open source, can you please link it? Assuming it's not… Can you possibly run a git bisect to help us figure out what React commit changed it? If you have a local clone of the repo, you should be able to run Thanks in advance for helping to investigate this. Best guess is something change with batching, but it's hard to tell. |
I'll try. Btw. is there a way to download this repo but without cmd |
@dragonflypl Can you try setting your git config to work with the proxy?
|
You should be able to try cloning over either https (in which case you can also use a proxy) or ssh. Not sure if both are blocked. If that doesn't work I would clone from a different network and then copy the whole folder between computers (including the hidden .git directory). In normal cases you could download the tgz from GitHub but since you need the history here that won't work. |
I did bisec and the output is:
I've double checked this & previous commit and I confirm that this one is the first where performance has dropped. |
@dragonflypl Thank you, this is really helpful. |
I can also confirm that our app performance has dropped since 95a313e |
@sebmarkbage Any idea what could cause a perf regression from your "Unfork Lazy Component Branches" commit? My instinct was incorrectly calling |
(cc @acdlite too) |
@dragonflypl @martinsb After looking at the blamed commit I'm still not sure what this could be. Is there any chance one of you can post a reduced repro case showing the regression? It would really help us to debug if we can reproduce it locally. We'll try to keep debugging but it will be hard to make good progress without a repro. |
I have a guess about what's going on here. We have a bunch of |
Hmm no, I'm wrong. |
Gah. You already tried that. 😆
|
This may be minor but is there a reason to have two functions which call one another instead of one with all the code? From a brief review of the commit the inner function doesn't seem to be used anywhere else but I may be missing the bigger picture. export function createFiberFromElement(
element: ReactElement,
mode: TypeOfMode,
expirationTime: ExpirationTime,
): Fiber {
const fiber = createFiberFromElementWithoutDebugInfo(
element,
mode,
expirationTime,
);
if (__DEV__) {
fiber._debugSource = element._source;
fiber._debugOwner = element._owner;
}
return fiber;
} |
Google Closure Compiler (which is part of our build step) should inline it. |
I think that I've noticed the same issue, here is how to reproduce:
Switching from uglify to terser fixed the issue: |
"Switching minifiers solves the problem" was not the conclusion I expected to find at the bottom of this thread... |
OK, I investigated. For reasons unknown, uglify has decided that now it should inline the FiberNode function into createFiber, so createFiber's implementation looks roughly like We should probably
|
Also – thank you @localvoid. |
In particular, piping (function() {
function FiberNode(a) {
this.tag = a;
}
function foo(a) {
return new FiberNode(a);
}
global.foo = foo;
})(); to
which is bad news. |
@dragonflypl To confirm that what you're seeing is the same issue, can you (a) check that you're using uglify-es and (b) try checking out the same commit 95a313e from your bisect then editing packages/react-reconciler/src/ReactFiber.js to add a "FiberNode.foobar = {};" on line 273 (after the definition of FiberNode) then rebuild and see if it fixes the issue in your app? I'm guessing you're using uglifyjs-webpack-plugin version 1. If you upgrade to version 2 (or to terser-webpack-plugin) then I think this issue will go away even on the release build of 16.6.0. |
If you use a function expression it won't inline it (function() {
var FiberNode = function FiberNode(a) {
this.tag = a;
}
function foo(a) {
return new FiberNode(a);
}
global.foo = foo;
})();
|
That's just not semantically equivalent. Seems like a pretty bad bug in |
|
It looks like something to do with It does not inline if the constructor function is used more than once: (function() {
function FiberNode(fiberNodeArg) {
this.tag = fiberNodeArg;
}
function foo(fooArg) {
return new FiberNode(fooArg);
}
function bar(barArg) {
return new FiberNode(barArg);
}
global.foo = foo;
global.bar = bar;
})(); !function(){function n(n){this.tag=n}global.foo=function(o){return new n(o)},global.bar=function(o){return new n(o)}}(); |
It’s rarely in a minifier’s best interest to inline variables/etc that are used many times, so that isn’t too surprising on its own. Note that 16.5.2 also calls the constructor only in one place. As @sebmarkbage points out though, it is incorrect to ever inline a function that “new” is applied to (unless it’s provable the instantiation runs at most once). It seems like uglify-es is known to be buggy and is not maintained. We should probably focus our efforts on shifting people off it. |
For reference: mishoo/UglifyJS#3156 (comment) uglify-es is stale
So, terser works correctly for this case, does not inline under
(function() {
function FiberNode(fiberNodeArg) {
this.tag = fiberNodeArg;
}
function foo(fooArg) {
return new FiberNode(fooArg);
}
// function bar(barArg) {
// return new FiberNode(barArg);
// }
global.foo = foo;
// global.bar = bar;
})(); !function(){function n(n){this.tag=n}global.foo=function(o){return new n(o)}}(); |
Thank you for the report and the solution. I hoped transitioning from |
I guess we can close this then. |
Is it really fair to close this? It seems the proposed solution is to transition people from uglify to terser, which is not feasible in the very short term. Additionally as reported above by @danburzo terser has memory issues. If it would be possible to edit the code (at least temporarily) to avoid the uglify-es bug that would be great. |
The solution is not to transition from There shouldn't be anything stopping you from reverting from |
Ah I see. I wasn't aware of those issues but I guess it's true uglify-es is
less widely used than uglify.
|
@danburzo Would you be up for raising an issue on the Terser repo and providing a reproduction case? From what I can see, you are the first person to hit this issue. |
Sorry, I meant In regards to terser, I'll follow up in the repo's issues with anything I can find, as to not derail this thread too much. |
@sophiebits : I confirm that
Thanks all for support! |
I can also confirm that switching from Webpack 4's |
Thank you for confieming! |
For anyone that needs a quick fix without having to upgrade to webpack 4 (because the terser-webpack-plugin only works webpack 4+) you can just turn off the new UglifyJsPlugin({
uglifyOptions: {
compress: {
reduce_funcs: false
}
}
}) to verify that it worked I did the following: cat test.js (function() {
function FiberNode(fiberNodeArg) {
this.tag = fiberNodeArg;
}
function foo(fooArg) {
return new FiberNode(fooArg);
}
// function bar(barArg) {
// return new FiberNode(barArg);
// }
global.foo = foo;
// global.bar = bar;
})(); This is how the output looks when using cat test.js | npx terser --compress --mangle !function(){function n(n){this.tag=n}global.foo=function(o){return new n(o)}}(); this is how the output looks by default with cat test.js | npx uglify-es --compress --mangle !function(){global.foo=function(n){return new function(n){this.tag=n}(n)}}();` (has the bug) and finally, this is how the output looks with cat test.js | npx uglify-es --compress reduce_funcs=false --mangle !function(){function n(n){this.tag=n}global.foo=function(o){return new n(o)}}(); (doesn't have the bug and is the same as what terser outputs) |
obviously, only do that ^ if you need a quick bug fix that has the least amount of change to your code. the real long-term fix is to get on a newer version of webpack which will use terser. |
Closes: CORE-2126 See my comment on the github thread for details facebook/react#13987 (comment) Test plan: * try canvas in a production environment * all pages that use React components should feel faster Change-Id: I3d9279784ef38bff1342ab6d20bbfefa697c92b2 Reviewed-on: https://gerrit.instructure.com/171633 Tested-by: Jenkins Reviewed-by: Clay Diffrient <cdiffrient@instructure.com> QA-Review: Ryan Shaw <ryan@instructure.com> Product-Review: Ryan Shaw <ryan@instructure.com>
I believe this issue should be made much more visible. I just got bitten by it. Also, because of it's nature, you might see it for the first time when pushing to production. Maybe it should be added to the docs/release notes? |
@uriklar Is correct. We found this issue once we pushed to prod. 50k user system. Not a great DX for us. |
Some time ago I did refactoring of cell renderers components to achieve performance gain (I have a huge table). I did refactoring from functional stateless components to
PureComponent
. E.g.:Now I saw that
React.memo
was released so I updated toreact@16.6.0
andreact-dom@16.6.0
(from16.5.2
) and refactored fromPureComponent
toReact.memo
with the expectation that it would be even faster (no lifecycle methods called, function smaller than class in memory etc...):And to my surprise, performance went significantly down.
Profile data in prod mode (no addons in chrome) show that there's much more scripting happening than before with
PureComponent
(scripting time for my case went from 0.5s to 3.8sek).After some investigation, it seems that it is not an issue with
React.memo
but with the new version of React. I've reverted cell renderers to PureComponent and left new react@16.6.0 version and a result (significantly slower performance) is still present.Do you have any idea what could be the issue with it?
The text was updated successfully, but these errors were encountered: