From 73d41b0c562024dba02827c8937d14526bcd6db1 Mon Sep 17 00:00:00 2001 From: Brian White Date: Fri, 1 May 2015 14:08:02 -0400 Subject: [PATCH] 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. --- benchmark/misc/next-tick-breadth-args.js | 37 +++++++ benchmark/misc/next-tick-depth-args.js | 48 +++++++++ src/node.js | 121 ++++++++++++++++++----- test/message/nexttick_throw.out | 1 + test/message/stdin_messages.out | 7 +- 5 files changed, 189 insertions(+), 25 deletions(-) create mode 100644 benchmark/misc/next-tick-breadth-args.js create mode 100644 benchmark/misc/next-tick-depth-args.js diff --git a/benchmark/misc/next-tick-breadth-args.js b/benchmark/misc/next-tick-breadth-args.js new file mode 100644 index 00000000000000..e617fcfc824f7f --- /dev/null +++ b/benchmark/misc/next-tick-breadth-args.js @@ -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); + } +} diff --git a/benchmark/misc/next-tick-depth-args.js b/benchmark/misc/next-tick-depth-args.js new file mode 100644 index 00000000000000..066b4837856f60 --- /dev/null +++ b/benchmark/misc/next-tick-depth-args.js @@ -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); +} diff --git a/src/node.js b/src/node.js index 1cb71c4927a2aa..5cf56f624225ee 100644 --- a/src/node.js +++ b/src/node.js @@ -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; diff --git a/test/message/nexttick_throw.out b/test/message/nexttick_throw.out index baced2ce879dea..1dd60694a892b8 100644 --- a/test/message/nexttick_throw.out +++ b/test/message/nexttick_throw.out @@ -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:*:*) diff --git a/test/message/stdin_messages.out b/test/message/stdin_messages.out index cbdc1fdb125626..92d09a0a144428 100644 --- a/test/message/stdin_messages.out +++ b/test/message/stdin_messages.out @@ -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