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 common = require('../common.js');
|
||||||
var bench = common.createBenchmark(main, {
|
var bench = common.createBenchmark(main, {
|
||||||
millions: [2]
|
millions: [12]
|
||||||
});
|
});
|
||||||
|
|
||||||
process.maxTickDepth = Infinity;
|
process.maxTickDepth = Infinity;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
var common = require('../common.js');
|
var common = require('../common.js');
|
||||||
var bench = common.createBenchmark(main, {
|
var bench = common.createBenchmark(main, {
|
||||||
millions: [2]
|
millions: [12]
|
||||||
});
|
});
|
||||||
|
|
||||||
process.maxTickDepth = Infinity;
|
process.maxTickDepth = Infinity;
|
||||||
|
@ -10,6 +10,42 @@ exports.setup = setupNextTick;
|
|||||||
// Will be overwritten when setupNextTick() is called.
|
// Will be overwritten when setupNextTick() is called.
|
||||||
exports.nextTick = null;
|
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() {
|
function setupNextTick() {
|
||||||
const async_wrap = process.binding('async_wrap');
|
const async_wrap = process.binding('async_wrap');
|
||||||
const async_hooks = require('async_hooks');
|
const async_hooks = require('async_hooks');
|
||||||
@ -27,7 +63,7 @@ function setupNextTick() {
|
|||||||
const { kInit, kBefore, kAfter, kDestroy, kAsyncUidCntr, kInitTriggerId } =
|
const { kInit, kBefore, kAfter, kDestroy, kAsyncUidCntr, kInitTriggerId } =
|
||||||
async_wrap.constants;
|
async_wrap.constants;
|
||||||
const { async_id_symbol, trigger_id_symbol } = async_wrap;
|
const { async_id_symbol, trigger_id_symbol } = async_wrap;
|
||||||
var nextTickQueue = [];
|
var nextTickQueue = new NextTickQueue();
|
||||||
var microtasksScheduled = false;
|
var microtasksScheduled = false;
|
||||||
|
|
||||||
// Used to run V8's micro task queue.
|
// Used to run V8's micro task queue.
|
||||||
@ -55,27 +91,29 @@ function setupNextTick() {
|
|||||||
function tickDone() {
|
function tickDone() {
|
||||||
if (tickInfo[kLength] !== 0) {
|
if (tickInfo[kLength] !== 0) {
|
||||||
if (tickInfo[kLength] <= tickInfo[kIndex]) {
|
if (tickInfo[kLength] <= tickInfo[kIndex]) {
|
||||||
nextTickQueue = [];
|
nextTickQueue.clear();
|
||||||
tickInfo[kLength] = 0;
|
tickInfo[kLength] = 0;
|
||||||
} else {
|
} else {
|
||||||
nextTickQueue.splice(0, tickInfo[kIndex]);
|
|
||||||
tickInfo[kLength] = nextTickQueue.length;
|
tickInfo[kLength] = nextTickQueue.length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tickInfo[kIndex] = 0;
|
tickInfo[kIndex] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const microTasksTickObject = {
|
||||||
|
callback: runMicrotasksCallback,
|
||||||
|
args: undefined,
|
||||||
|
domain: null,
|
||||||
|
[async_id_symbol]: 0,
|
||||||
|
[trigger_id_symbol]: 0
|
||||||
|
};
|
||||||
function scheduleMicrotasks() {
|
function scheduleMicrotasks() {
|
||||||
if (microtasksScheduled)
|
if (microtasksScheduled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const tickObject =
|
|
||||||
new TickObject(runMicrotasksCallback, undefined, null);
|
|
||||||
// For the moment all microtasks come from the void until the PromiseHook
|
// For the moment all microtasks come from the void until the PromiseHook
|
||||||
// API is implemented.
|
// API is implemented.
|
||||||
tickObject[async_id_symbol] = 0;
|
nextTickQueue.push(microTasksTickObject);
|
||||||
tickObject[trigger_id_symbol] = 0;
|
|
||||||
nextTickQueue.push(tickObject);
|
|
||||||
|
|
||||||
tickInfo[kLength]++;
|
tickInfo[kLength]++;
|
||||||
microtasksScheduled = true;
|
microtasksScheduled = true;
|
||||||
@ -86,8 +124,9 @@ function setupNextTick() {
|
|||||||
_runMicrotasks();
|
_runMicrotasks();
|
||||||
|
|
||||||
if (tickInfo[kIndex] < tickInfo[kLength] ||
|
if (tickInfo[kIndex] < tickInfo[kLength] ||
|
||||||
emitPendingUnhandledRejections())
|
emitPendingUnhandledRejections()) {
|
||||||
scheduleMicrotasks();
|
scheduleMicrotasks();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _combinedTickCallback(args, callback) {
|
function _combinedTickCallback(args, callback) {
|
||||||
@ -133,7 +172,8 @@ function setupNextTick() {
|
|||||||
function _tickCallback() {
|
function _tickCallback() {
|
||||||
do {
|
do {
|
||||||
while (tickInfo[kIndex] < tickInfo[kLength]) {
|
while (tickInfo[kIndex] < tickInfo[kLength]) {
|
||||||
const tock = nextTickQueue[tickInfo[kIndex]++];
|
++tickInfo[kIndex];
|
||||||
|
const tock = nextTickQueue.shift();
|
||||||
const callback = tock.callback;
|
const callback = tock.callback;
|
||||||
const args = tock.args;
|
const args = tock.args;
|
||||||
|
|
||||||
@ -174,7 +214,8 @@ function setupNextTick() {
|
|||||||
function _tickDomainCallback() {
|
function _tickDomainCallback() {
|
||||||
do {
|
do {
|
||||||
while (tickInfo[kIndex] < tickInfo[kLength]) {
|
while (tickInfo[kIndex] < tickInfo[kLength]) {
|
||||||
const tock = nextTickQueue[tickInfo[kIndex]++];
|
++tickInfo[kIndex];
|
||||||
|
const tock = nextTickQueue.shift();
|
||||||
const callback = tock.callback;
|
const callback = tock.callback;
|
||||||
const domain = tock.domain;
|
const domain = tock.domain;
|
||||||
const args = tock.args;
|
const args = tock.args;
|
||||||
@ -210,45 +251,48 @@ function setupNextTick() {
|
|||||||
} while (tickInfo[kLength] !== 0);
|
} while (tickInfo[kLength] !== 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TickObject(callback, args, domain) {
|
class TickObject {
|
||||||
this.callback = callback;
|
constructor(callback, args, asyncId, triggerAsyncId) {
|
||||||
this.domain = domain;
|
this.callback = callback;
|
||||||
this.args = args;
|
this.args = args;
|
||||||
this[async_id_symbol] = -1;
|
this.domain = process.domain || null;
|
||||||
this[trigger_id_symbol] = -1;
|
this[async_id_symbol] = asyncId;
|
||||||
}
|
this[trigger_id_symbol] = triggerAsyncId;
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `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) {
|
function nextTick(callback) {
|
||||||
if (typeof callback !== 'function')
|
if (typeof callback !== 'function')
|
||||||
throw new errors.TypeError('ERR_INVALID_CALLBACK');
|
throw new errors.TypeError('ERR_INVALID_CALLBACK');
|
||||||
// on the way out, don't bother. it won't get fired anyway.
|
|
||||||
if (process._exiting)
|
if (process._exiting)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var args;
|
var args;
|
||||||
if (arguments.length > 1) {
|
switch (arguments.length) {
|
||||||
args = new Array(arguments.length - 1);
|
case 1: break;
|
||||||
for (var i = 1; i < arguments.length; i++)
|
case 2: args = [arguments[1]]; break;
|
||||||
args[i - 1] = arguments[i];
|
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);
|
const asyncId = ++async_uid_fields[kAsyncUidCntr];
|
||||||
setupInit(obj, null);
|
const triggerAsyncId = initTriggerId();
|
||||||
|
const obj = new TickObject(callback, args, asyncId, triggerAsyncId);
|
||||||
nextTickQueue.push(obj);
|
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) {
|
function internalNextTick(triggerAsyncId, callback) {
|
||||||
if (typeof callback !== 'function')
|
if (typeof callback !== 'function')
|
||||||
throw new TypeError('callback is not a function');
|
throw new TypeError('callback is not a function');
|
||||||
@ -259,17 +303,25 @@ function setupNextTick() {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var args;
|
var args;
|
||||||
if (arguments.length > 2) {
|
switch (arguments.length) {
|
||||||
args = new Array(arguments.length - 2);
|
case 2: break;
|
||||||
for (var i = 2; i < arguments.length; i++)
|
case 3: args = [arguments[2]]; break;
|
||||||
args[i - 2] = arguments[i];
|
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);
|
const asyncId = ++async_uid_fields[kAsyncUidCntr];
|
||||||
setupInit(obj, triggerAsyncId);
|
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.
|
// The call to initTriggerId() was skipped, so clear kInitTriggerId.
|
||||||
async_uid_fields[kInitTriggerId] = 0;
|
async_uid_fields[kInitTriggerId] = 0;
|
||||||
nextTickQueue.push(obj);
|
|
||||||
tickInfo[kLength]++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user