-
Notifications
You must be signed in to change notification settings - Fork 48.6k
Use Rest Parameters where it makes sense #637
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
Conversation
The first commit should go away after it was pulled, right? |
Could you run performance tests on this (especially since the actual use of arguments is out of our control)? |
Yes but how? :) |
Yea, that first commit will go away. http://paulshen.github.io/react-bench/ has some benchmarks. Also, let's turn on the |
@jordwalke: This uses arguments under-the-hood anyway, it's mostly just sugar for doing so at this point... |
it is really just Array.prototype.slice.call(arguments, offsetA, offsetB). |
@jeffmo: I've seen diffs far less concerning than this cause huge performance regressions. This diff touches the single most frequently invoked method in the entire React core (the constructor for all components). No matter how innocuous seeming, I always do a perf test for any diff that touches the critical path. |
Gotcha, fair enough. Question remains about how to do so though, I think? |
(also somewhat surprised that we're using |
Believe me the performance of that was well contested when people trusted micro-benchmarks more than real world usage. I'd be very hesitant to mess with that optimization. The next bottleneck is getting the multi-var arguments from the convenience constructor into here. Potentially through another constructor function. Thankfully modern engines will help us there. |
@sebmarkbage: Certain uses of |
children.forEach(validateChildKeys); | ||
} | ||
if (children.length === 1) { | ||
this.props.children = children[0]; |
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.
You may also want to benchmark just storing this as children
always. The original motivation was to avoid the array allocation but if you're going to always have it it might not make sense to bother with the unwrapping.
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 actually have quite a bit of code that depends on it being a single child. :/
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.
Ahh right, forgot about composite components looking at children.
So we create an additional array every time the method is invoked? Regardless of what the benchmarks say, maybe we should hold off on rest args for |
We really need a higher-level metric to shoot for here. Gut-feeling on whether "another allocation" is ok or not is only going to lead to more subjective debate and potential for mis-guided optimization effort. I think perf questions are valid (including here) -- as long as we have a concrete way to answer them. I'm a little worried that we're heading towards a tangential meta-discussion right now. @cpojer asked earlier what the best way to perf-test this would be -- do we have an answer for this? If not, we're not doing ourselves any favors in speculating here and leaving red-tape around "sensitive" core functions. |
Pull away! |
Code looks good -- I'll leave it to @jordwalke to merge in case he has any further concerns |
} | ||
return className; | ||
function joinClasses(...classes) { | ||
return classes.filter((className) => !!className).join(' '); |
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.
Have you done any perf comparisons?
I'm not completely sure, but it looks like you're creating a new function here and then creating an unnecessary array just so that you can turn it into a string. Pretty? sure. But I don't think that's really worth the cost
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.
Amdahl's law, etc
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.
return classes.filter(Boolean).join(' ')
?
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 hope escape analysis (does it already work in modern js engines?) would help to eliminate array allocation...
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 think it is fine.
As said, I ran react-perf and it didn't become slower than before.
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 jsperf linked in the docblock has very different results for using array vs args (also different across browsers...).
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'm super unhappy but I perf-tested this and it was about 94 times slower. Not awesome. JS engines, get your shit together. Anyway, I just reverted the change here for until 2020 when at least one browser will have ES6 natively.
@cpojer What's the perf results in IE8? I agree that we don't have enough good tests and tools to do perf testing reliably right now. That means that we're often best suited to use our own intuition atm. That means we shouldn't block on potentially misguided optimization efforts. However, this code has been through a lot of testing in individual products which have been fine tuned. This was not found to be a bottle neck. Changing this code path may or may not cause a regression in those products. Something we may not discover much later. IMO, it needs to be a clear win for this refactoring to take the place of the tested code. I.e. a new API surface or clear readability win. IMHO, this is mostly subjective and unnecessary risk. |
One important reason is that arguments are not really inferable statically, while rest params are. @jeffmo will fill you in. Ideally after ES6 becomes a standard, arguments will just cease to exist. |
If this was new code I wouldn't have a problem with it. Since neither had been tested and we'd just be speculating. @cpojer that would've been good to have in the initial description. If this is something we imminently need for static analysis. Let's continue that offline. |
Alright, just rebased this and reverted the changes to ReactComponent for now so that this change can be pulled. |
We know that this is harmless with equal confidence that we know it's harmful. Yay ignorance-induced red tape! |
Use Rest Parameters where it makes sense
Depends on #636
This changes a bunch functions to use rest parameters instead of 'arguments'.
It makes the code simpler to read and ensures we always work with arrays instead of an imposter.