Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

node: improve nextTick performance #1571

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might add a case for process.nextTick(cb) by itself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I should merge the pre-existing non-args benchmark with this one?

}
}
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a nit: would you mind adding a comment briefly explaining why this call is separated into a switch statement, for future readers?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

// 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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

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