Skip to content

Commit e30fc2c

Browse files
mscdexaddaleax
authored andcommitted
process: improve nextTick() performance
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]>
1 parent 864abc5 commit e30fc2c

File tree

3 files changed

+97
-45
lines changed

3 files changed

+97
-45
lines changed

benchmark/process/next-tick-depth-args.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
var common = require('../common.js');
44
var bench = common.createBenchmark(main, {
5-
millions: [2]
5+
millions: [12]
66
});
77

88
process.maxTickDepth = Infinity;

benchmark/process/next-tick-depth.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict';
22
var common = require('../common.js');
33
var bench = common.createBenchmark(main, {
4-
millions: [2]
4+
millions: [12]
55
});
66

77
process.maxTickDepth = Infinity;

lib/internal/process/next_tick.js

+95-43
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,42 @@ exports.setup = setupNextTick;
1010
// Will be overwritten when setupNextTick() is called.
1111
exports.nextTick = null;
1212

13+
class NextTickQueue {
14+
constructor() {
15+
this.head = null;
16+
this.tail = null;
17+
this.length = 0;
18+
}
19+
20+
push(v) {
21+
const entry = { data: v, next: null };
22+
if (this.length > 0)
23+
this.tail.next = entry;
24+
else
25+
this.head = entry;
26+
this.tail = entry;
27+
++this.length;
28+
}
29+
30+
shift() {
31+
if (this.length === 0)
32+
return;
33+
const ret = this.head.data;
34+
if (this.length === 1)
35+
this.head = this.tail = null;
36+
else
37+
this.head = this.head.next;
38+
--this.length;
39+
return ret;
40+
}
41+
42+
clear() {
43+
this.head = null;
44+
this.tail = null;
45+
this.length = 0;
46+
}
47+
}
48+
1349
function setupNextTick() {
1450
const async_wrap = process.binding('async_wrap');
1551
const async_hooks = require('async_hooks');
@@ -27,7 +63,7 @@ function setupNextTick() {
2763
const { kInit, kBefore, kAfter, kDestroy, kAsyncUidCntr, kInitTriggerId } =
2864
async_wrap.constants;
2965
const { async_id_symbol, trigger_id_symbol } = async_wrap;
30-
var nextTickQueue = [];
66+
var nextTickQueue = new NextTickQueue();
3167
var microtasksScheduled = false;
3268

3369
// Used to run V8's micro task queue.
@@ -55,27 +91,29 @@ function setupNextTick() {
5591
function tickDone() {
5692
if (tickInfo[kLength] !== 0) {
5793
if (tickInfo[kLength] <= tickInfo[kIndex]) {
58-
nextTickQueue = [];
94+
nextTickQueue.clear();
5995
tickInfo[kLength] = 0;
6096
} else {
61-
nextTickQueue.splice(0, tickInfo[kIndex]);
6297
tickInfo[kLength] = nextTickQueue.length;
6398
}
6499
}
65100
tickInfo[kIndex] = 0;
66101
}
67102

103+
const microTasksTickObject = {
104+
callback: runMicrotasksCallback,
105+
args: undefined,
106+
domain: null,
107+
[async_id_symbol]: 0,
108+
[trigger_id_symbol]: 0
109+
};
68110
function scheduleMicrotasks() {
69111
if (microtasksScheduled)
70112
return;
71113

72-
const tickObject =
73-
new TickObject(runMicrotasksCallback, undefined, null);
74114
// For the moment all microtasks come from the void until the PromiseHook
75115
// API is implemented.
76-
tickObject[async_id_symbol] = 0;
77-
tickObject[trigger_id_symbol] = 0;
78-
nextTickQueue.push(tickObject);
116+
nextTickQueue.push(microTasksTickObject);
79117

80118
tickInfo[kLength]++;
81119
microtasksScheduled = true;
@@ -86,8 +124,9 @@ function setupNextTick() {
86124
_runMicrotasks();
87125

88126
if (tickInfo[kIndex] < tickInfo[kLength] ||
89-
emitPendingUnhandledRejections())
127+
emitPendingUnhandledRejections()) {
90128
scheduleMicrotasks();
129+
}
91130
}
92131

93132
function _combinedTickCallback(args, callback) {
@@ -133,7 +172,8 @@ function setupNextTick() {
133172
function _tickCallback() {
134173
do {
135174
while (tickInfo[kIndex] < tickInfo[kLength]) {
136-
const tock = nextTickQueue[tickInfo[kIndex]++];
175+
++tickInfo[kIndex];
176+
const tock = nextTickQueue.shift();
137177
const callback = tock.callback;
138178
const args = tock.args;
139179

@@ -174,7 +214,8 @@ function setupNextTick() {
174214
function _tickDomainCallback() {
175215
do {
176216
while (tickInfo[kIndex] < tickInfo[kLength]) {
177-
const tock = nextTickQueue[tickInfo[kIndex]++];
217+
++tickInfo[kIndex];
218+
const tock = nextTickQueue.shift();
178219
const callback = tock.callback;
179220
const domain = tock.domain;
180221
const args = tock.args;
@@ -210,45 +251,48 @@ function setupNextTick() {
210251
} while (tickInfo[kLength] !== 0);
211252
}
212253

213-
function TickObject(callback, args, domain) {
214-
this.callback = callback;
215-
this.domain = domain;
216-
this.args = args;
217-
this[async_id_symbol] = -1;
218-
this[trigger_id_symbol] = -1;
219-
}
220-
221-
function setupInit(tickObject, triggerAsyncId) {
222-
tickObject[async_id_symbol] = ++async_uid_fields[kAsyncUidCntr];
223-
tickObject[trigger_id_symbol] = triggerAsyncId || initTriggerId();
224-
if (async_hook_fields[kInit] > 0) {
225-
emitInit(tickObject[async_id_symbol],
226-
'TickObject',
227-
tickObject[trigger_id_symbol],
228-
tickObject);
254+
class TickObject {
255+
constructor(callback, args, asyncId, triggerAsyncId) {
256+
this.callback = callback;
257+
this.args = args;
258+
this.domain = process.domain || null;
259+
this[async_id_symbol] = asyncId;
260+
this[trigger_id_symbol] = triggerAsyncId;
229261
}
230262
}
231263

264+
// `nextTick()` will not enqueue any callback when the process is about to
265+
// exit since the callback would not have a chance to be executed.
232266
function nextTick(callback) {
233267
if (typeof callback !== 'function')
234268
throw new errors.TypeError('ERR_INVALID_CALLBACK');
235-
// on the way out, don't bother. it won't get fired anyway.
269+
236270
if (process._exiting)
237271
return;
238272

239273
var args;
240-
if (arguments.length > 1) {
241-
args = new Array(arguments.length - 1);
242-
for (var i = 1; i < arguments.length; i++)
243-
args[i - 1] = arguments[i];
274+
switch (arguments.length) {
275+
case 1: break;
276+
case 2: args = [arguments[1]]; break;
277+
case 3: args = [arguments[1], arguments[2]]; break;
278+
case 4: args = [arguments[1], arguments[2], arguments[3]]; break;
279+
default:
280+
args = new Array(arguments.length - 1);
281+
for (var i = 1; i < arguments.length; i++)
282+
args[i - 1] = arguments[i];
244283
}
245284

246-
var obj = new TickObject(callback, args, process.domain || null);
247-
setupInit(obj, null);
285+
const asyncId = ++async_uid_fields[kAsyncUidCntr];
286+
const triggerAsyncId = initTriggerId();
287+
const obj = new TickObject(callback, args, asyncId, triggerAsyncId);
248288
nextTickQueue.push(obj);
249-
tickInfo[kLength]++;
289+
++tickInfo[kLength];
290+
if (async_hook_fields[kInit] > 0)
291+
emitInit(asyncId, 'TickObject', triggerAsyncId, obj);
250292
}
251293

294+
// `internalNextTick()` will not enqueue any callback when the process is
295+
// about to exit since the callback would not have a chance to be executed.
252296
function internalNextTick(triggerAsyncId, callback) {
253297
if (typeof callback !== 'function')
254298
throw new TypeError('callback is not a function');
@@ -259,17 +303,25 @@ function setupNextTick() {
259303
return;
260304

261305
var args;
262-
if (arguments.length > 2) {
263-
args = new Array(arguments.length - 2);
264-
for (var i = 2; i < arguments.length; i++)
265-
args[i - 2] = arguments[i];
306+
switch (arguments.length) {
307+
case 2: break;
308+
case 3: args = [arguments[2]]; break;
309+
case 4: args = [arguments[2], arguments[3]]; break;
310+
case 5: args = [arguments[2], arguments[3], arguments[4]]; break;
311+
default:
312+
args = new Array(arguments.length - 2);
313+
for (var i = 2; i < arguments.length; i++)
314+
args[i - 2] = arguments[i];
266315
}
267316

268-
var obj = new TickObject(callback, args, process.domain || null);
269-
setupInit(obj, triggerAsyncId);
317+
const asyncId = ++async_uid_fields[kAsyncUidCntr];
318+
const obj = new TickObject(callback, args, asyncId, triggerAsyncId);
319+
nextTickQueue.push(obj);
320+
++tickInfo[kLength];
321+
if (async_hook_fields[kInit] > 0)
322+
emitInit(asyncId, 'TickObject', triggerAsyncId, obj);
323+
270324
// The call to initTriggerId() was skipped, so clear kInitTriggerId.
271325
async_uid_fields[kInitTriggerId] = 0;
272-
nextTickQueue.push(obj);
273-
tickInfo[kLength]++;
274326
}
275327
}

0 commit comments

Comments
 (0)