Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 36.4k
process: improve nextTick() performance#13446
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
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -10,6 +10,42 @@ exports.setup = setupNextTick; | ||
| // Will be overwritten when setupNextTick() is called. | ||
| exports.nextTick = null; | ||
| class NextTickQueue { | ||
| constructor() { | ||
| this.head = null; | ||
| this.tail = null; | ||
| this.length = 0; | ||
| } | ||
| push(v) { | ||
| const entry = { data: v, next: null }; | ||
| if (this.length > 0) | ||
| this.tail.next = entry; | ||
| else | ||
| this.head = entry; | ||
| this.tail = entry; | ||
| ++this.length; | ||
| } | ||
| shift() { | ||
| if (this.length === 0) | ||
| return; | ||
| const ret = this.head.data; | ||
| if (this.length === 1) | ||
| this.head = this.tail = null; | ||
| else | ||
| this.head = this.head.next; | ||
| --this.length; | ||
| return ret; | ||
| } | ||
| clear() { | ||
| this.head = null; | ||
| this.tail = null; | ||
| this.length = 0; | ||
| } | ||
| } | ||
| function setupNextTick() { | ||
| const async_wrap = process.binding('async_wrap'); | ||
| const async_hooks = require('async_hooks'); | ||
| @@ -27,7 +63,7 @@ function setupNextTick() { | ||
| const { kInit, kBefore, kAfter, kDestroy, kAsyncUidCntr, kInitTriggerId } = | ||
| async_wrap.constants; | ||
| const { async_id_symbol, trigger_id_symbol } = async_wrap; | ||
| var nextTickQueue = []; | ||
| var nextTickQueue = new NextTickQueue(); | ||
| var microtasksScheduled = false; | ||
| // Used to run V8's micro task queue. | ||
| @@ -55,27 +91,29 @@ function setupNextTick() { | ||
| function tickDone() { | ||
| if (tickInfo[kLength] !== 0) { | ||
| if (tickInfo[kLength] <= tickInfo[kIndex]) { | ||
| nextTickQueue = []; | ||
| nextTickQueue.clear(); | ||
| tickInfo[kLength] = 0; | ||
| } else { | ||
| nextTickQueue.splice(0, tickInfo[kIndex]); | ||
| tickInfo[kLength] = nextTickQueue.length; | ||
| } | ||
| } | ||
| tickInfo[kIndex] = 0; | ||
| } | ||
| const microTasksTickObject = { | ||
| callback: runMicrotasksCallback, | ||
| args: undefined, | ||
| domain: null, | ||
| [async_id_symbol]: 0, | ||
| [trigger_id_symbol]: 0 | ||
| }; | ||
| function scheduleMicrotasks() { | ||
| if (microtasksScheduled) | ||
| return; | ||
| const tickObject = | ||
| new TickObject(runMicrotasksCallback, undefined, null); | ||
| // For the moment all microtasks come from the void until the PromiseHook | ||
| // API is implemented. | ||
| tickObject[async_id_symbol] = 0; | ||
| tickObject[trigger_id_symbol] = 0; | ||
| nextTickQueue.push(tickObject); | ||
| nextTickQueue.push(microTasksTickObject); | ||
| tickInfo[kLength]++; | ||
| microtasksScheduled = true; | ||
| @@ -86,8 +124,9 @@ function setupNextTick() { | ||
| _runMicrotasks(); | ||
| if (tickInfo[kIndex] < tickInfo[kLength] || | ||
| emitPendingUnhandledRejections()) | ||
| emitPendingUnhandledRejections()) { | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just wondering... It this a style choice or a DEOPT? ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
| scheduleMicrotasks(); | ||
| } | ||
| } | ||
| function _combinedTickCallback(args, callback) { | ||
| @@ -133,7 +172,8 @@ function setupNextTick() { | ||
| function _tickCallback() { | ||
| do { | ||
| while (tickInfo[kIndex] < tickInfo[kLength]) { | ||
| const tock = nextTickQueue[tickInfo[kIndex]++]; | ||
| ++tickInfo[kIndex]; | ||
| const tock = nextTickQueue.shift(); | ||
| const callback = tock.callback; | ||
| const args = tock.args; | ||
| @@ -174,7 +214,8 @@ function setupNextTick() { | ||
| function _tickDomainCallback() { | ||
| do { | ||
| while (tickInfo[kIndex] < tickInfo[kLength]) { | ||
| const tock = nextTickQueue[tickInfo[kIndex]++]; | ||
| ++tickInfo[kIndex]; | ||
| const tock = nextTickQueue.shift(); | ||
| const callback = tock.callback; | ||
| const domain = tock.domain; | ||
| const args = tock.args; | ||
| @@ -210,45 +251,48 @@ function setupNextTick() { | ||
| } while (tickInfo[kLength] !== 0); | ||
| } | ||
| function TickObject(callback, args, domain) { | ||
| this.callback = callback; | ||
| this.domain = domain; | ||
| this.args = args; | ||
| this[async_id_symbol] = -1; | ||
| this[trigger_id_symbol] = -1; | ||
| } | ||
| function setupInit(tickObject, triggerAsyncId) { | ||
| tickObject[async_id_symbol] = ++async_uid_fields[kAsyncUidCntr]; | ||
| tickObject[trigger_id_symbol] = triggerAsyncId || initTriggerId(); | ||
| if (async_hook_fields[kInit] > 0) { | ||
| emitInit(tickObject[async_id_symbol], | ||
| 'TickObject', | ||
| tickObject[trigger_id_symbol], | ||
| tickObject); | ||
| class TickObject { | ||
| constructor(callback, args, asyncId, triggerAsyncId) { | ||
| this.callback = callback; | ||
| this.args = args; | ||
| this.domain = process.domain || null; | ||
| this[async_id_symbol] = asyncId; | ||
| this[trigger_id_symbol] = triggerAsyncId; | ||
| } | ||
| } | ||
| // `nextTick()` will not enqueue any callback when the process is about to | ||
| // exit since the callback would not have a chance to be executed. | ||
| 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. | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we clarify the comment rather than removing it? ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removing it matches Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FWIW Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
| ||
| if (process._exiting) | ||
| return; | ||
| var args; | ||
| if (arguments.length > 1) { | ||
| args = new Array(arguments.length - 1); | ||
| for (var i = 1; i < arguments.length; i++) | ||
| args[i - 1] = arguments[i]; | ||
| switch (arguments.length) { | ||
| case 1: break; | ||
| case 2: args = [arguments[1]]; break; | ||
| case 3: args = [arguments[1], arguments[2]]; break; | ||
| case 4: args = [arguments[1], arguments[2], arguments[3]]; break; | ||
| default: | ||
| args = new Array(arguments.length - 1); | ||
| for (var i = 1; i < arguments.length; i++) | ||
| args[i - 1] = arguments[i]; | ||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is faster than spread parameters even with V8 5.9? ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. Member There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
| } | ||
| var obj = new TickObject(callback, args, process.domain || null); | ||
| setupInit(obj, null); | ||
| const asyncId = ++async_uid_fields[kAsyncUidCntr]; | ||
| const triggerAsyncId = initTriggerId(); | ||
| const obj = new TickObject(callback, args, asyncId, triggerAsyncId); | ||
| nextTickQueue.push(obj); | ||
| tickInfo[kLength]++; | ||
| ++tickInfo[kLength]; | ||
| if (async_hook_fields[kInit] > 0) | ||
| emitInit(asyncId, 'TickObject', triggerAsyncId, obj); | ||
| } | ||
| // `internalNextTick()` will not enqueue any callback when the process is | ||
| // about to exit since the callback would not have a chance to be executed. | ||
| function internalNextTick(triggerAsyncId, callback) { | ||
| if (typeof callback !== 'function') | ||
| throw new TypeError('callback is not a function'); | ||
| @@ -259,17 +303,25 @@ function setupNextTick() { | ||
| return; | ||
| var args; | ||
| if (arguments.length > 2) { | ||
| args = new Array(arguments.length - 2); | ||
| for (var i = 2; i < arguments.length; i++) | ||
| args[i - 2] = arguments[i]; | ||
| switch (arguments.length) { | ||
| case 2: break; | ||
| case 3: args = [arguments[2]]; break; | ||
| case 4: args = [arguments[2], arguments[3]]; break; | ||
| case 5: args = [arguments[2], arguments[3], arguments[4]]; break; | ||
| default: | ||
| args = new Array(arguments.length - 2); | ||
| for (var i = 2; i < arguments.length; i++) | ||
| args[i - 2] = arguments[i]; | ||
| } | ||
| var obj = new TickObject(callback, args, process.domain || null); | ||
| setupInit(obj, triggerAsyncId); | ||
| const asyncId = ++async_uid_fields[kAsyncUidCntr]; | ||
| const obj = new TickObject(callback, args, asyncId, triggerAsyncId); | ||
| nextTickQueue.push(obj); | ||
| ++tickInfo[kLength]; | ||
| if (async_hook_fields[kInit] > 0) | ||
| emitInit(asyncId, 'TickObject', triggerAsyncId, obj); | ||
| // The call to initTriggerId() was skipped, so clear kInitTriggerId. | ||
| async_uid_fields[kInitTriggerId] = 0; | ||
| nextTickQueue.push(obj); | ||
| 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.
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
***.Uh oh!
There was an error while loading. Please reload this page.
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).