Skip to content

Commit 6aef146

Browse files
committed
node: improve nextTick performance
This commit uses separate functions to isolate deopts caused by try-catches and avoids fn.apply() for callbacks with small numbers of arguments. These changes improve performance by ~1-40% in the various nextTick benchmarks.
1 parent 0450ce7 commit 6aef146

File tree

5 files changed

+178
-25
lines changed

5 files changed

+178
-25
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
var common = require('../common.js');
3+
var bench = common.createBenchmark(main, {
4+
millions: [2]
5+
});
6+
7+
function main(conf) {
8+
var N = +conf.millions * 1e6;
9+
var n = 0;
10+
11+
function cb1(arg1) {
12+
n++;
13+
if (n === N)
14+
bench.end(n / 1e6);
15+
}
16+
function cb2(arg1, arg2) {
17+
n++;
18+
if (n === N)
19+
bench.end(n / 1e6);
20+
}
21+
function cb3(arg1, arg2, arg3) {
22+
n++;
23+
if (n === N)
24+
bench.end(n / 1e6);
25+
}
26+
27+
bench.start();
28+
for (var i = 0; i < N; i++) {
29+
if (i % 3 === 0)
30+
process.nextTick(cb3, 512, true, null);
31+
else if (i % 2 === 0)
32+
process.nextTick(cb2, false, 5.1);
33+
else
34+
process.nextTick(cb1, 0);
35+
}
36+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
var common = require('../common.js');
2+
var bench = common.createBenchmark(main, {
3+
millions: [2]
4+
});
5+
6+
process.maxTickDepth = Infinity;
7+
8+
function main(conf) {
9+
var n = +conf.millions * 1e6;
10+
11+
function cb3(arg1, arg2, arg3) {
12+
if (--n) {
13+
if (n % 3 === 0)
14+
process.nextTick(cb3, 512, true, null);
15+
else if (n % 2 === 0)
16+
process.nextTick(cb2, false, 5.1);
17+
else
18+
process.nextTick(cb1, 0);
19+
} else
20+
bench.end(+conf.millions);
21+
}
22+
function cb2(arg1, arg2) {
23+
if (--n) {
24+
if (n % 3 === 0)
25+
process.nextTick(cb3, 512, true, null);
26+
else if (n % 2 === 0)
27+
process.nextTick(cb2, false, 5.1);
28+
else
29+
process.nextTick(cb1, 0);
30+
} else
31+
bench.end(+conf.millions);
32+
}
33+
function cb1(arg1) {
34+
if (--n) {
35+
if (n % 3 === 0)
36+
process.nextTick(cb3, 512, true, null);
37+
else if (n % 2 === 0)
38+
process.nextTick(cb2, false, 5.1);
39+
else
40+
process.nextTick(cb1, 0);
41+
} else
42+
bench.end(+conf.millions);
43+
}
44+
bench.start();
45+
process.nextTick(cb1, true);
46+
}

src/node.js

Lines changed: 91 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -328,22 +328,29 @@
328328
// Run callbacks that have no domain.
329329
// Using domains will cause this to be overridden.
330330
function _tickCallback() {
331-
var callback, threw, tock;
331+
var callback, args, tock;
332332

333333
do {
334334
while (tickInfo[kIndex] < tickInfo[kLength]) {
335335
tock = nextTickQueue[tickInfo[kIndex]++];
336336
callback = tock.callback;
337-
threw = true;
338-
try {
339-
if (tock.args === undefined)
340-
callback();
341-
else
342-
callback.apply(null, tock.args);
343-
threw = false;
344-
} finally {
345-
if (threw)
346-
tickDone();
337+
args = tock.args;
338+
if (args === undefined)
339+
doNTCallback0(callback);
340+
else {
341+
switch (args.length) {
342+
case 1:
343+
doNTCallback1(callback, args[0]);
344+
break;
345+
case 2:
346+
doNTCallback2(callback, args[0], args[1]);
347+
break;
348+
case 3:
349+
doNTCallback3(callback, args[0], args[1], args[2]);
350+
break;
351+
default:
352+
doNTCallbackMany(callback, args);
353+
}
347354
}
348355
if (1e4 < tickInfo[kIndex])
349356
tickDone();
@@ -355,25 +362,32 @@
355362
}
356363

357364
function _tickDomainCallback() {
358-
var callback, domain, threw, tock;
365+
var callback, domain, args, tock;
359366

360367
do {
361368
while (tickInfo[kIndex] < tickInfo[kLength]) {
362369
tock = nextTickQueue[tickInfo[kIndex]++];
363370
callback = tock.callback;
364371
domain = tock.domain;
372+
args = tock.args;
365373
if (domain)
366374
domain.enter();
367-
threw = true;
368-
try {
369-
if (tock.args === undefined)
370-
callback();
371-
else
372-
callback.apply(null, tock.args);
373-
threw = false;
374-
} finally {
375-
if (threw)
376-
tickDone();
375+
if (args === undefined)
376+
doNTCallback0(callback);
377+
else {
378+
switch (args.length) {
379+
case 1:
380+
doNTCallback1(callback, args[0]);
381+
break;
382+
case 2:
383+
doNTCallback2(callback, args[0], args[1]);
384+
break;
385+
case 3:
386+
doNTCallback3(callback, args[0], args[1], args[2]);
387+
break;
388+
default:
389+
doNTCallbackMany(callback, args);
390+
}
377391
}
378392
if (1e4 < tickInfo[kIndex])
379393
tickDone();
@@ -386,6 +400,61 @@
386400
} while (tickInfo[kLength] !== 0);
387401
}
388402

403+
function doNTCallback0(callback) {
404+
var threw = true;
405+
try {
406+
callback();
407+
threw = false;
408+
} finally {
409+
if (threw)
410+
tickDone();
411+
}
412+
}
413+
414+
function doNTCallback1(callback, arg1) {
415+
var threw = true;
416+
try {
417+
callback(arg1);
418+
threw = false;
419+
} finally {
420+
if (threw)
421+
tickDone();
422+
}
423+
}
424+
425+
function doNTCallback2(callback, arg1, arg2) {
426+
var threw = true;
427+
try {
428+
callback(arg1, arg2);
429+
threw = false;
430+
} finally {
431+
if (threw)
432+
tickDone();
433+
}
434+
}
435+
436+
function doNTCallback3(callback, arg1, arg2, arg3) {
437+
var threw = true;
438+
try {
439+
callback(arg1, arg2, arg3);
440+
threw = false;
441+
} finally {
442+
if (threw)
443+
tickDone();
444+
}
445+
}
446+
447+
function doNTCallbackMany(callback, args) {
448+
var threw = true;
449+
try {
450+
callback.apply(null, args);
451+
threw = false;
452+
} finally {
453+
if (threw)
454+
tickDone();
455+
}
456+
}
457+
389458
function TickObject(c, args) {
390459
this.callback = c;
391460
this.domain = process.domain || null;

test/message/nexttick_throw.out

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
^
55
ReferenceError: undefined_reference_error_maker is not defined
66
at *test*message*nexttick_throw.js:*:*
7+
at doNTCallback0 (node.js:*:*)
78
at process._tickCallback (node.js:*:*)
89
at Function.Module.runMain (module.js:*:*)
910
at startup (node.js:*:*)

test/message/stdin_messages.out

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ SyntaxError: Strict mode code may not include a with statement
1212
at emitNone (events.js:*:*)
1313
at Socket.emit (events.js:*:*)
1414
at endReadableNT (_stream_readable.js:*:*)
15+
at doNTCallback2 (node.js:*:*)
1516
at process._tickCallback (node.js:*:*)
1617
42
1718
42
@@ -29,7 +30,7 @@ Error: hello
2930
at emitNone (events.js:*:*)
3031
at Socket.emit (events.js:*:*)
3132
at endReadableNT (_stream_readable.js:*:*)
32-
at process._tickCallback (node.js:*:*)
33+
at doNTCallback2 (node.js:*:*)
3334

3435
[stdin]:1
3536
throw new Error("hello")
@@ -44,7 +45,7 @@ Error: hello
4445
at emitNone (events.js:*:*)
4546
at Socket.emit (events.js:*:*)
4647
at endReadableNT (_stream_readable.js:*:*)
47-
at process._tickCallback (node.js:*:*)
48+
at doNTCallback2 (node.js:*:*)
4849
100
4950

5051
[stdin]:1
@@ -60,7 +61,7 @@ ReferenceError: y is not defined
6061
at emitNone (events.js:*:*)
6162
at Socket.emit (events.js:*:*)
6263
at endReadableNT (_stream_readable.js:*:*)
63-
at process._tickCallback (node.js:*:*)
64+
at doNTCallback2 (node.js:*:*)
6465

6566
[stdin]:1
6667
var ______________________________________________; throw 10

0 commit comments

Comments
 (0)