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.
// Using domains will cause this to be overridden.
function _tickCallback() {
var callback, threw, tock;
var callback, args, tock;
do {
while (tickInfo[kIndex] < tickInfo[kLength]) {
tock = nextTickQueue[tickInfo[kIndex]++];
callback = tock.callback;
threw = true;
try {
if (tock.args === undefined)
callback();
else
callback.apply(null, tock.args);
threw = false;
} finally {
if (threw)
tickDone();
args = tock.args;
// Using separate callback execution functions helps to limit the
// scope of DEOPTs caused by using try blocks and allows direct
// callback invocation with small numbers of arguments to avoid the
// performance hit associated with using `fn.apply()`
if (args === undefined) {
doNTCallback0(callback);
} else {
switch (args.length) {
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])
tickDone();
@ -355,25 +366,36 @@
}
function _tickDomainCallback() {
var callback, domain, threw, tock;
var callback, domain, args, tock;
do {
while (tickInfo[kIndex] < tickInfo[kLength]) {
tock = nextTickQueue[tickInfo[kIndex]++];
callback = tock.callback;
domain = tock.domain;
args = tock.args;
if (domain)
domain.enter();
threw = true;
try {
if (tock.args === undefined)
callback();
else
callback.apply(null, tock.args);
threw = false;
} finally {
if (threw)
tickDone();
// Using separate callback execution functions helps to limit the
// scope of DEOPTs caused by using try blocks and allows direct
// callback invocation with small numbers of arguments to avoid the
// performance hit associated with using `fn.apply()`
if (args === undefined) {
doNTCallback0(callback);
} else {
switch (args.length) {
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])
tickDone();
@ -386,6 +408,61 @@
} 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) {
this.callback = c;
this.domain = process.domain || null;

View File

@ -4,6 +4,7 @@
^
ReferenceError: undefined_reference_error_maker is not defined
at *test*message*nexttick_throw.js:*:*
at doNTCallback0 (node.js:*:*)
at process._tickCallback (node.js:*:*)
at Function.Module.runMain (module.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 Socket.emit (events.js:*:*)
at endReadableNT (_stream_readable.js:*:*)
at doNTCallback2 (node.js:*:*)
at process._tickCallback (node.js:*:*)
42
42
@ -29,7 +30,7 @@ Error: hello
at emitNone (events.js:*:*)
at Socket.emit (events.js:*:*)
at endReadableNT (_stream_readable.js:*:*)
at process._tickCallback (node.js:*:*)
at doNTCallback2 (node.js:*:*)
[stdin]:1
throw new Error("hello")
@ -44,7 +45,7 @@ Error: hello
at emitNone (events.js:*:*)
at Socket.emit (events.js:*:*)
at endReadableNT (_stream_readable.js:*:*)
at process._tickCallback (node.js:*:*)
at doNTCallback2 (node.js:*:*)
100
[stdin]:1
@ -60,7 +61,7 @@ ReferenceError: y is not defined
at emitNone (events.js:*:*)
at Socket.emit (events.js:*:*)
at endReadableNT (_stream_readable.js:*:*)
at process._tickCallback (node.js:*:*)
at doNTCallback2 (node.js:*:*)
[stdin]:1
var ______________________________________________; throw 10