process: improve nextTick() performance
PR-URL: https://github.com/nodejs/node/pull/13446 Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com> Reviewed-By: Evan Lucas <evanlucas@me.com>
This commit is contained in:
parent
1e2905f46a
commit
460ee75f7e
@ -2,7 +2,7 @@
|
||||
|
||||
var common = require('../common.js');
|
||||
var bench = common.createBenchmark(main, {
|
||||
millions: [2]
|
||||
millions: [12]
|
||||
});
|
||||
|
||||
process.maxTickDepth = Infinity;
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
var common = require('../common.js');
|
||||
var bench = common.createBenchmark(main, {
|
||||
millions: [2]
|
||||
millions: [12]
|
||||
});
|
||||
|
||||
process.maxTickDepth = Infinity;
|
||||
|
@ -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()) {
|
||||
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.
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
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]++;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user