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.

PR-URL: https://github.com/iojs/io.js/pull/1571
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Chris Dickinson <christopher.s.dickinson@gmail.com>
This commit is contained in:
Brian White 2015-05-01 14:08:02 -04:00
parent ea5195ccaf
commit c7782c0af8
5 changed files with 189 additions and 25 deletions

View File

@ -0,0 +1,37 @@
'use strict';
var common = require('../common.js');
var bench = common.createBenchmark(main, {
millions: [2]
});
function main(conf) {
var N = +conf.millions * 1e6;
var n = 0;
function cb1(arg1) {
n++;
if (n === N)
bench.end(n / 1e6);
}
function cb2(arg1, arg2) {
n++;
if (n === N)
bench.end(n / 1e6);
}
function cb3(arg1, arg2, arg3) {
n++;
if (n === N)
bench.end(n / 1e6);
}
bench.start();
for (var i = 0; i < N; i++) {
if (i % 3 === 0)
process.nextTick(cb3, 512, true, null);
else if (i % 2 === 0)
process.nextTick(cb2, false, 5.1);
else
process.nextTick(cb1, 0);
}
}

View File

@ -0,0 +1,48 @@
'use strict';
var common = require('../common.js');
var bench = common.createBenchmark(main, {
millions: [2]
});
process.maxTickDepth = Infinity;
function main(conf) {
var n = +conf.millions * 1e6;
function cb3(arg1, arg2, arg3) {
if (--n) {
if (n % 3 === 0)
process.nextTick(cb3, 512, true, null);
else if (n % 2 === 0)
process.nextTick(cb2, false, 5.1);
else
process.nextTick(cb1, 0);
} else
bench.end(+conf.millions);
}
function cb2(arg1, arg2) {
if (--n) {
if (n % 3 === 0)
process.nextTick(cb3, 512, true, null);
else if (n % 2 === 0)
process.nextTick(cb2, false, 5.1);
else
process.nextTick(cb1, 0);
} else
bench.end(+conf.millions);
}
function cb1(arg1) {
if (--n) {
if (n % 3 === 0)
process.nextTick(cb3, 512, true, null);
else if (n % 2 === 0)
process.nextTick(cb2, false, 5.1);
else
process.nextTick(cb1, 0);
} else
bench.end(+conf.millions);
}
bench.start();
process.nextTick(cb1, true);
}

View File

@ -328,22 +328,33 @@
// Run callbacks that have no domain. // Run callbacks that have no domain.
// Using domains will cause this to be overridden. // Using domains will cause this to be overridden.
function _tickCallback() { function _tickCallback() {
var callback, threw, tock; var callback, args, tock;
do { do {
while (tickInfo[kIndex] < tickInfo[kLength]) { while (tickInfo[kIndex] < tickInfo[kLength]) {
tock = nextTickQueue[tickInfo[kIndex]++]; tock = nextTickQueue[tickInfo[kIndex]++];
callback = tock.callback; callback = tock.callback;
threw = true; args = tock.args;
try { // Using separate callback execution functions helps to limit the
if (tock.args === undefined) // scope of DEOPTs caused by using try blocks and allows direct
callback(); // callback invocation with small numbers of arguments to avoid the
else // performance hit associated with using `fn.apply()`
callback.apply(null, tock.args); if (args === undefined) {
threw = false; doNTCallback0(callback);
} finally { } else {
if (threw) switch (args.length) {
tickDone(); case 1:
doNTCallback1(callback, args[0]);
break;
case 2:
doNTCallback2(callback, args[0], args[1]);
break;
case 3:
doNTCallback3(callback, args[0], args[1], args[2]);
break;
default:
doNTCallbackMany(callback, args);
}
} }
if (1e4 < tickInfo[kIndex]) if (1e4 < tickInfo[kIndex])
tickDone(); tickDone();
@ -355,25 +366,36 @@
} }
function _tickDomainCallback() { function _tickDomainCallback() {
var callback, domain, threw, tock; var callback, domain, args, tock;
do { do {
while (tickInfo[kIndex] < tickInfo[kLength]) { while (tickInfo[kIndex] < tickInfo[kLength]) {
tock = nextTickQueue[tickInfo[kIndex]++]; tock = nextTickQueue[tickInfo[kIndex]++];
callback = tock.callback; callback = tock.callback;
domain = tock.domain; domain = tock.domain;
args = tock.args;
if (domain) if (domain)
domain.enter(); domain.enter();
threw = true; // Using separate callback execution functions helps to limit the
try { // scope of DEOPTs caused by using try blocks and allows direct
if (tock.args === undefined) // callback invocation with small numbers of arguments to avoid the
callback(); // performance hit associated with using `fn.apply()`
else if (args === undefined) {
callback.apply(null, tock.args); doNTCallback0(callback);
threw = false; } else {
} finally { switch (args.length) {
if (threw) case 1:
tickDone(); doNTCallback1(callback, args[0]);
break;
case 2:
doNTCallback2(callback, args[0], args[1]);
break;
case 3:
doNTCallback3(callback, args[0], args[1], args[2]);
break;
default:
doNTCallbackMany(callback, args);
}
} }
if (1e4 < tickInfo[kIndex]) if (1e4 < tickInfo[kIndex])
tickDone(); tickDone();
@ -386,6 +408,61 @@
} while (tickInfo[kLength] !== 0); } while (tickInfo[kLength] !== 0);
} }
function doNTCallback0(callback) {
var threw = true;
try {
callback();
threw = false;
} finally {
if (threw)
tickDone();
}
}
function doNTCallback1(callback, arg1) {
var threw = true;
try {
callback(arg1);
threw = false;
} finally {
if (threw)
tickDone();
}
}
function doNTCallback2(callback, arg1, arg2) {
var threw = true;
try {
callback(arg1, arg2);
threw = false;
} finally {
if (threw)
tickDone();
}
}
function doNTCallback3(callback, arg1, arg2, arg3) {
var threw = true;
try {
callback(arg1, arg2, arg3);
threw = false;
} finally {
if (threw)
tickDone();
}
}
function doNTCallbackMany(callback, args) {
var threw = true;
try {
callback.apply(null, args);
threw = false;
} finally {
if (threw)
tickDone();
}
}
function TickObject(c, args) { function TickObject(c, args) {
this.callback = c; this.callback = c;
this.domain = process.domain || null; this.domain = process.domain || null;

View File

@ -4,6 +4,7 @@
^ ^
ReferenceError: undefined_reference_error_maker is not defined ReferenceError: undefined_reference_error_maker is not defined
at *test*message*nexttick_throw.js:*:* at *test*message*nexttick_throw.js:*:*
at doNTCallback0 (node.js:*:*)
at process._tickCallback (node.js:*:*) at process._tickCallback (node.js:*:*)
at Function.Module.runMain (module.js:*:*) at Function.Module.runMain (module.js:*:*)
at startup (node.js:*:*) at startup (node.js:*:*)

View File

@ -12,6 +12,7 @@ SyntaxError: Strict mode code may not include a with statement
at emitNone (events.js:*:*) at emitNone (events.js:*:*)
at Socket.emit (events.js:*:*) at Socket.emit (events.js:*:*)
at endReadableNT (_stream_readable.js:*:*) at endReadableNT (_stream_readable.js:*:*)
at doNTCallback2 (node.js:*:*)
at process._tickCallback (node.js:*:*) at process._tickCallback (node.js:*:*)
42 42
42 42
@ -29,7 +30,7 @@ Error: hello
at emitNone (events.js:*:*) at emitNone (events.js:*:*)
at Socket.emit (events.js:*:*) at Socket.emit (events.js:*:*)
at endReadableNT (_stream_readable.js:*:*) at endReadableNT (_stream_readable.js:*:*)
at process._tickCallback (node.js:*:*) at doNTCallback2 (node.js:*:*)
[stdin]:1 [stdin]:1
throw new Error("hello") throw new Error("hello")
@ -44,7 +45,7 @@ Error: hello
at emitNone (events.js:*:*) at emitNone (events.js:*:*)
at Socket.emit (events.js:*:*) at Socket.emit (events.js:*:*)
at endReadableNT (_stream_readable.js:*:*) at endReadableNT (_stream_readable.js:*:*)
at process._tickCallback (node.js:*:*) at doNTCallback2 (node.js:*:*)
100 100
[stdin]:1 [stdin]:1
@ -60,7 +61,7 @@ ReferenceError: y is not defined
at emitNone (events.js:*:*) at emitNone (events.js:*:*)
at Socket.emit (events.js:*:*) at Socket.emit (events.js:*:*)
at endReadableNT (_stream_readable.js:*:*) at endReadableNT (_stream_readable.js:*:*)
at process._tickCallback (node.js:*:*) at doNTCallback2 (node.js:*:*)
[stdin]:1 [stdin]:1
var ______________________________________________; throw 10 var ______________________________________________; throw 10