-
Notifications
You must be signed in to change notification settings - Fork 29.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
process: improve nextTick() performance #13446
Conversation
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.
LGTM (just some nits)
@@ -86,8 +89,9 @@ function setupNextTick() { | |||
_runMicrotasks(); | |||
|
|||
if (tickInfo[kIndex] < tickInfo[kLength] || | |||
emitPendingUnhandledRejections()) | |||
emitPendingUnhandledRejections()) { |
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.
Just wondering... It this a style choice or a DEOPT?
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 choice, I think we tend to use braces for multi-line conditionals from what I've generally seen, this is just one "offender" I ran across while working in this file.
lib/internal/process/next_tick.js
Outdated
this.domain = process.domain || null; | ||
this[async_id_symbol] = asyncId; | ||
this[trigger_id_symbol] = triggerId; | ||
if (async_hook_fields[kInit] > 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.
I'm not 100% with so much side effects in a constructor...
If it's not a performance hit I'd rather see these four lines in a static method or factory
If it is a performance hit, I think it should be documented:
// These side effects were folded into the c'tor for performance
(or something to that effect)
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 can't move these other statements back into nextTick()
without causing a performance regression from what I saw. I believe it's because of Crankshaft's inlining limits, but I did not want to introduce regressions at this moment (we will have to deal with it once TurboFan is everywhere).
Unfortunately internalNextTick()
exceeds these limits, so there's not much I can do 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.
Yeah so probably even a comment will "irritate" Crankshaft
What can you do 🤷♂️
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.
Hopefully once V8 (or perhaps they already do in later versions) is able to better optimize computed properties in TurboFan, we can use plain objects again without losing performance.
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.
lgtm w comments
} | ||
} | ||
|
||
function nextTick(callback) { | ||
if (typeof callback !== 'function') | ||
throw new errors.TypeError('ERR_INVALID_CALLBACK'); | ||
// on the way out, don't bother. it won't get fired anyway. | ||
|
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.
Can we clarify the comment rather than removing it?
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.
Removing it matches internalNextTick()
and it doesn't seem like a particularly helpful comment as process._exiting
is pretty self-explanatory to me.
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.
Yes, the code says what but I'm wondering if it may be helpful to say why.
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.
FWIW Crankshaft
can decide not to inline a function because it has a comment in it 😞 so if we come up with a better comment it probably should go in line 231.
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.
Eh, it makes little difference as the comment was already there and that will soon be an obsolete optimization as I understand.
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 don't understand what you mean by a 'why comment.'
AIUI it's a comment explaining why we're doing this (as opposed to explaining what this code does).
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.
@gibfahn I understand that part. What I meant was I don't understand what this is in reference to. Is it the process._exiting
line? The changes I'm making? Something else? What is the suggested 'why comment' ?
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.
What is the suggested 'why comment' ?
(AIUI) This is the comment, it's a "why comment", it's helpful to preserve it.
// on the way out, don't bother. it won't get fired anyway.
FWIW I'm sure putting it above the function is fine.
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.
@Fishrock123 @gibfahn I've added something above both functions now, let me know if that is what you had in mind or not.
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.
@Fishrock123 @gibfahn I've added something above both functions now, let me know if that is what you had in mind or not.
Clearer than the original, thanks.
lib/internal/process/next_tick.js
Outdated
tickObject); | ||
class TickObject { | ||
constructor(callback, args, triggerId) { | ||
var asyncId = ++async_uid_fields[kAsyncUidCntr]; |
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.
Is there a reason this is var
over const
?
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 was triggering a deopt.
lib/internal/process/next_tick.js
Outdated
if (async_hook_fields[kInit] > 0) | ||
emitInit(asyncId, 'TickObject', triggerId, this); | ||
nextTickQueue.push(this); | ||
tickInfo[kLength]++; | ||
} | ||
} | ||
|
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 bail fast `if (process._exiting)` since the loop has effectively stopped
// and there will never be a `nextTick`
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.
@Fishrock123 PTAL
@@ -2,7 +2,7 @@ | |||
|
|||
var common = require('../common.js'); | |||
var bench = common.createBenchmark(main, { | |||
millions: [2] | |||
millions: [12] |
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.
Why this increase? You are getting ridiculous statistical confidence. By some quick calculation, this should only improve the confidence by a factor of 2.5. Compensating for that and the previous version should also give you confidence well beyond ***
.
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 always choose something large enough to allow for any re-opts to occur and for results to stabilize. With 2
, I was getting results that varied quite a bit in comparison because it was finishing so quickly (at least on my machine).
ddc2508
to
447162b
Compare
FWIW with V8 5.9 in master I now get these results with this PR as-is:
I'm not sure how breadth-args was so much higher previously, I would've expected results more like I am seeing now in 5.9, where the args and non-args are much closer together. |
447162b
to
d68f8ba
Compare
Ok, I've made a couple more adjustments:
Here are the results as of these latest changes:
|
@refack @Fishrock123 @jasnell @evanlucas Please review these recent changes. |
default: | ||
args = new Array(arguments.length - 1); | ||
for (var i = 1; i < arguments.length; i++) | ||
args[i - 1] = arguments[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.
This is faster than spread parameters even with V8 5.9?
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 not sure how spread could be used here. Do you mean rest parameters? If so, I tried that and while it's a little faster in one benchmark, it's slower to varying degrees in all others.
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, that's what I meant. Sorry, could never get all the terminologies right.
So I'm just writing down what I saw (writing down mostly to organize my thoughts):
Anything else of significance? |
@refack That's mostly it, unless you want to also count the switch to ES6 class for |
Thanks for the confirmation ✔️ |
d68f8ba
to
2126cad
Compare
Rebased. If there are no objections in the next 24 hours I will assume all who approved the original changes also approve of these latest changes that I previously outlined. |
PR-URL: nodejs#13446 Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]> Reviewed-By: Evan Lucas <[email protected]>
2126cad
to
460ee75
Compare
PR-URL: #13446 Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]> Reviewed-By: Evan Lucas <[email protected]>
PR-URL: #13446 Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]> Reviewed-By: Evan Lucas <[email protected]>
PR-URL: #13446 Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]> Reviewed-By: Evan Lucas <[email protected]>
PR-URL: #13446 Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]> Reviewed-By: Evan Lucas <[email protected]>
PR-URL: #13446 Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]> Reviewed-By: Evan Lucas <[email protected]>
PR-URL: #13446 Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]> Reviewed-By: Evan Lucas <[email protected]>
PR-URL: #13446 Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]> Reviewed-By: Evan Lucas <[email protected]>
I recently noticed that with the introduction of Async Hooks also came the (perhaps unintentional) reverting of 804d57d. I actually tried a lot of different solutions, but almost all of them resulted in regressions in other
nextTick()
benchmarks (e.g. a perf boost in no args case, but regression in args case) which I still don't quite understand. One thing I did notice is that the ideal solution of just using a plain object and embedding the symbol properties as computed properties causes TurboFan to kick in and that causes a significant slowdown fornextTick()
.I was able to improve the existing non-ES6-based constructor solution, but it turns out using an ES6 class improves performance even more (even surpassing the plain object-based solutions that performed better in whichever benchmarks), so that is what I decided on.
Results:
CI: https://ci.nodejs.org/job/node-test-pull-request/8464/
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)