You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
It becomes more strange with while cycle added in the end (this cycle removes the difference in the previous cycle, but it almost doubles its run time besides):
This is expected. We first need warm-up and only tier-up once we have a couple of cycles in the relevant function. Also note that there's only one deoptimization in forEach, and that doesn't really affect the outcome.
In your first example, the console.log(Date.now() - start) triggers a deopt because apparently V8 doesn't infer the return type of Date.now() correctly ("Insufficient type feedback for LHS of binary operation.")
When you remove it, it then deopts because of the const func = () => {} statement. If you move that out of the function, it stays optimized. I don't understand why that should be the case, though.
The let in for (let i = ...) slows it down quite a bit. If you replace it with var, it's about 4x faster.
Here is a revised version that has stable performance characteristics.
@bmeurer Per #9729 (comment) - in particular, moving func out of the function and Date.now()'s return type not being inferred - do you still feel that's expected behavior?
Date.now() seems like a missed optimization opportunity. The func thing I can't explain and I couldn't piece it together from reading V8's source code. I would appreciate it if you can provide insight.
Ah, the func is easy to explain: Currently we can only inline at a call site when there was exactly one closure, which is why the first iteration is super fast, but as soon as we see more than one closure we have to emit a call. I'm working on that, see crbug.com/v8/2206.
And finally, the let/const vs var issue: It's because Crankshaft permanently disables optimizations for the function when it sees a hole (for let/const TDZ), which is exactly what happens when you wrap the code above in a closure (as explained by @ofrobots on the relevant V8 bug report). One way to fix this would be consistently use only Ignition+TurboFan for let/const.
Interesting, thanks. I had a more-or-less accurate mental image of the let-vs-var issue but I was looking in completely the wrong place for the func deopt.
And finally with crrev.com/2525243002let and const now go to TurboFan+Ignition only, so the performance cliff is gone, and let/const are safe to use for you (with next LTS; this is not really back-mergable unfortunately).
Reacted by Vse Mozhe Buty, Gibson Fahnestock, Filip Szczechowiak, Arnaud Benhamdine, Félix Dion-Robidoux, Kris Kaczor, Oleksii Orlenko and Daniel Abromeit aka Abro
Reacted by Gibson Fahnestock, Hugues Malphettes, Arnaud Benhamdine and Félix Dion-Robidoux
Activity
vsemozhetbyt commentedon Nov 21, 2016
If the
whilecycle comes first, the difference remains in all the cycles and a run time is not doubled in any of them:vsemozhetbyt commentedon Nov 22, 2016
With non-empty functions that return different values, the difference remains (with less ratio, though):
vsemozhetbyt commentedon Nov 22, 2016
However, if the function declaration is removed from cycles, the difference disappears:
At the same time, the "
whilecycle at the last position increases own and previous run times" issue remains and gains strength:vsemozhetbyt commentedon Nov 22, 2016
And some more
whilecycle weirdness:whilecycle with one iteration has the described impact:whileovercomes the trailing one:[-]The first iteration in cycles runs much faster than others[/-][+]Strange deoptimzations in cycles[/+]vsemozhetbyt commentedon Nov 22, 2016
In the ES5 code the difference ratio is bigger:
However, the
whileweirdness disappears:vsemozhetbyt commentedon Nov 22, 2016
Issue distribution:
sam-github commentedon Nov 22, 2016
This is a pure V8 question, or possibly bug report, should be reported upstream to theml
vsemozhetbyt commentedon Nov 22, 2016
Reported.
bmeurer commentedon Nov 22, 2016
This is expected. We first need warm-up and only tier-up once we have a couple of cycles in the relevant function. Also note that there's only one deoptimization in forEach, and that doesn't really affect the outcome.
bnoordhuis commentedon Nov 22, 2016
In your first example, the
console.log(Date.now() - start)triggers a deopt because apparently V8 doesn't infer the return type of Date.now() correctly ("Insufficient type feedback for LHS of binary operation.")When you remove it, it then deopts because of the
const func = () => {}statement. If you move that out of the function, it stays optimized. I don't understand why that should be the case, though.The
letinfor (let i = ...)slows it down quite a bit. If you replace it withvar, it's about 4x faster.Here is a revised version that has stable performance characteristics.
vsemozhetbyt commentedon Nov 22, 2016
@bmeurer, @bnoordhuis Thank you!
However, how would you explain this remaining
whileweirdness:vsemozhetbyt commentedon Nov 22, 2016
Oops. This is not the
while. It is theletorconst!With non-empty cycle the ratio increases from 2 to 10:
Is this also intended or should I post a separate bug upstream?
vsemozhetbyt commentedon Nov 23, 2016
Reported just in case.
bnoordhuis commentedon Nov 23, 2016
@bmeurer Per #9729 (comment) - in particular, moving
funcout of the function andDate.now()'s return type not being inferred - do you still feel that's expected behavior?Date.now()seems like a missed optimization opportunity. Thefuncthing I can't explain and I couldn't piece it together from reading V8's source code. I would appreciate it if you can provide insight.bmeurer commentedon Nov 24, 2016
@bnoordhuis The
Date.now()deopt seems a bit stupid, indeed. Fixed that for TurboFan in crrev.com/2528853003.I'll have a look at the
functhing.bmeurer commentedon Nov 24, 2016
Ah, the
funcis easy to explain: Currently we can only inline at a call site when there was exactly one closure, which is why the first iteration is super fast, but as soon as we see more than one closure we have to emit a call. I'm working on that, see crbug.com/v8/2206.[turbofan] Infer proper type for calls to Date.now.
bmeurer commentedon Nov 24, 2016
And finally, the
let/constvsvarissue: It's because Crankshaft permanently disables optimizations for the function when it sees a hole (for let/const TDZ), which is exactly what happens when you wrap the code above in a closure (as explained by @ofrobots on the relevant V8 bug report). One way to fix this would be consistently use only Ignition+TurboFan for let/const.bnoordhuis commentedon Nov 24, 2016
Interesting, thanks. I had a more-or-less accurate mental image of the let-vs-var issue but I was looking in completely the wrong place for the
funcdeopt.bmeurer commentedon Nov 24, 2016
You're welcome. I'll see if I can push a bit harder for the inlining fixes.
[compiler] Consistently use Ignition+TurboFan for lexical variables.
bmeurer commentedon Dec 2, 2016
And finally with crrev.com/2525243002
letandconstnow go to TurboFan+Ignition only, so the performance cliff is gone, andlet/constare safe to use for you (with next LTS; this is not really back-mergable unfortunately).