Skip to content

Commit fa69a24

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 fa69a24

File tree

5 files changed

+172
-25
lines changed

5 files changed

+172
-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: 85 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -328,23 +328,32 @@
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, threw, 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+
threw = doNTCallback0(callback);
340+
else {
341+
switch (args.length) {
342+
case 1:
343+
threw = doNTCallback1(callback, args[0]);
344+
break;
345+
case 2:
346+
threw = doNTCallback2(callback, args[0], args[1]);
347+
break;
348+
case 3:
349+
threw = doNTCallback3(callback, args[0], args[1], args[2]);
350+
break;
351+
default:
352+
threw = doNTCallbackMany(callback, args);
353+
}
347354
}
355+
if (threw)
356+
tickDone();
348357
if (1e4 < tickInfo[kIndex])
349358
tickDone();
350359
}
@@ -355,26 +364,35 @@
355364
}
356365

357366
function _tickDomainCallback() {
358-
var callback, domain, threw, tock;
367+
var callback, domain, args, threw, tock;
359368

360369
do {
361370
while (tickInfo[kIndex] < tickInfo[kLength]) {
362371
tock = nextTickQueue[tickInfo[kIndex]++];
363372
callback = tock.callback;
364373
domain = tock.domain;
374+
args = tock.args;
365375
if (domain)
366376
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();
377+
if (args === undefined)
378+
threw = doNTCallback0(callback);
379+
else {
380+
switch (args.length) {
381+
case 1:
382+
threw = doNTCallback1(callback, args[0]);
383+
break;
384+
case 2:
385+
threw = doNTCallback2(callback, args[0], args[1]);
386+
break;
387+
case 3:
388+
threw = doNTCallback3(callback, args[0], args[1], args[2]);
389+
break;
390+
default:
391+
threw = doNTCallbackMany(callback, args);
392+
}
377393
}
394+
if (threw)
395+
tickDone();
378396
if (1e4 < tickInfo[kIndex])
379397
tickDone();
380398
if (domain)
@@ -386,6 +404,51 @@
386404
} while (tickInfo[kLength] !== 0);
387405
}
388406

407+
function doNTCallback0(callback) {
408+
var threw = true;
409+
try {
410+
callback();
411+
threw = false;
412+
} finally {}
413+
return threw;
414+
}
415+
416+
function doNTCallback1(callback, arg1) {
417+
var threw = true;
418+
try {
419+
callback(arg1);
420+
threw = false;
421+
} finally {}
422+
return threw;
423+
}
424+
425+
function doNTCallback2(callback, arg1, arg2) {
426+
var threw = true;
427+
try {
428+
callback(arg1, arg2);
429+
threw = false;
430+
} finally {}
431+
return threw;
432+
}
433+
434+
function doNTCallback3(callback, arg1, arg2, arg3) {
435+
var threw = true;
436+
try {
437+
callback(arg1, arg2, arg3);
438+
threw = false;
439+
} finally {}
440+
return threw;
441+
}
442+
443+
function doNTCallbackMany(callback, args) {
444+
var threw = true;
445+
try {
446+
callback.apply(null, args);
447+
threw = false;
448+
} finally {}
449+
return threw;
450+
}
451+
389452
function TickObject(c, args) {
390453
this.callback = c;
391454
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)