Skip to content

Commit

Permalink
node: improve nextTick performance
Browse files Browse the repository at this point in the history
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: #1571
Reviewed-By: Trevor Norris <[email protected]>
Reviewed-By: Chris Dickinson <[email protected]>
  • Loading branch information
mscdex committed May 2, 2015
1 parent ea5195c commit c7782c0
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 25 deletions.
37 changes: 37 additions & 0 deletions benchmark/misc/next-tick-breadth-args.js
Original file line number Diff line number Diff line change
@@ -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);
}
}
48 changes: 48 additions & 0 deletions benchmark/misc/next-tick-depth-args.js
Original file line number Diff line number Diff line change
@@ -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);
}
121 changes: 99 additions & 22 deletions src/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions test/message/nexttick_throw.out
Original file line number Diff line number Diff line change
Expand Up @@ -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:*:*)
Expand Down
7 changes: 4 additions & 3 deletions test/message/stdin_messages.out
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
Expand All @@ -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
Expand All @@ -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
Expand Down

0 comments on commit c7782c0

Please sign in to comment.