From 44243ad9f4adc36e1099c41d9f38f1669acf9cf1 Mon Sep 17 00:00:00 2001 From: Andras Date: Tue, 26 Apr 2016 10:33:19 -0400 Subject: [PATCH 01/12] timers: linkedlist optimizations use L.create() factory to create access-optimized linkedlist objects --- lib/internal/linkedlist.js | 18 ++++++++++++++++-- lib/timers.js | 6 ++---- test/parallel/test-timers-linked-list.js | 5 +++++ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/internal/linkedlist.js b/lib/internal/linkedlist.js index 02186cfedcb9f6..33ada550efb62f 100644 --- a/lib/internal/linkedlist.js +++ b/lib/internal/linkedlist.js @@ -6,6 +6,13 @@ function init(list) { } exports.init = init; +// create a new linked list +function create() { + var list = { _idleNext: null, _idlePrev: null }; + init(list); + return list; +} +exports.create = create; // show the most idle item function peek(list) { @@ -42,10 +49,17 @@ exports.remove = remove; // remove a item from its list and place at the end. function append(list, item) { - remove(item); + if (item._idleNext || item._idlePrev) { + remove(item); + } + + // items are linked with _idleNext -> (older) and _idlePrev -> (newer) + // TODO: swap the linkage to match the intuitive older items at "prev" item._idleNext = list._idleNext; - list._idleNext._idlePrev = item; item._idlePrev = list; + + // the list _idleNext points to tail (newest) and _idlePrev to head (oldest) + list._idleNext._idlePrev = item; list._idleNext = item; } exports.append = append; diff --git a/lib/timers.js b/lib/timers.js index dc2506e01e09a4..7ae25f6a32513d 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -502,16 +502,14 @@ Timeout.prototype.close = function() { }; -var immediateQueue = {}; -L.init(immediateQueue); +var immediateQueue = L.create(); function processImmediate() { var queue = immediateQueue; var domain, immediate; - immediateQueue = {}; - L.init(immediateQueue); + immediateQueue = L.create(); while (L.isEmpty(queue) === false) { immediate = L.shift(queue); diff --git a/test/parallel/test-timers-linked-list.js b/test/parallel/test-timers-linked-list.js index b5ff9f56bf0700..cdae64d344a303 100644 --- a/test/parallel/test-timers-linked-list.js +++ b/test/parallel/test-timers-linked-list.js @@ -103,3 +103,8 @@ assert.equal(C, L.shift(list)); // list assert.ok(L.isEmpty(list)); +var list2 = L.create(); +var list3 = L.create(); +assert.ok(L.isEmpty(list2)); +assert.ok(L.isEmpty(list3)); +assert.ok(list2 != list3); From d005b8193e968f73c6fbb5a3ea8bb79897a3f8da Mon Sep 17 00:00:00 2001 From: Andras Date: Wed, 27 Apr 2016 20:31:59 -0400 Subject: [PATCH 02/12] timers: 3x faster setImmediate Save the setImmediate callback arguments into an array instead of a closure, and invoke the callback on the arguments from an optimizable function. 60% faster setImmediate with 0 args (15% if self-recursive) 4x faster setImmediate with 1-3 args, 2x with > 3 seems to be faster with less memory pressure when memory is tight Changes: - use L.create() to build faster lists - use runCallback() from within tryOnImmediate - create immediate timers with a function instead of new - just save the arguments and not build closures for the callbacks --- lib/timers.js | 70 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/lib/timers.js b/lib/timers.js index 7ae25f6a32513d..6ed5221336cfca 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -515,9 +515,13 @@ function processImmediate() { immediate = L.shift(queue); domain = immediate.domain; + if (!immediate._onImmediate) + continue; + if (domain) domain.enter(); + immediate._callback = immediate._onImmediate; tryOnImmediate(immediate, queue); if (domain) @@ -538,7 +542,8 @@ function processImmediate() { function tryOnImmediate(immediate, queue) { var threw = true; try { - immediate._onImmediate(); + // make the actual call outside the try/catch to allow it to be optimized + runCallback(immediate); threw = false; } finally { if (threw && !L.isEmpty(queue)) { @@ -552,14 +557,36 @@ function tryOnImmediate(immediate, queue) { } } +function runCallback(timer) { + var argv = timer._argv; + var argc = argv ? argv.length : 0; + switch (argc) { + // fast-path callbacks with 0-3 arguments + case 0: + return timer._callback(); + case 1: + return timer._callback(argv[0]); + case 2: + return timer._callback(argv[0], argv[1]); + case 3: + return timer._callback(argv[0], argv[1], argv[2]); + // more then 3 arguments run slower with .apply + default: + return timer._callback.apply(timer, argv); + } +} -function Immediate() { } - -Immediate.prototype.domain = undefined; -Immediate.prototype._onImmediate = undefined; -Immediate.prototype._idleNext = undefined; -Immediate.prototype._idlePrev = undefined; +function createImmediate(callback) { + return { + _idleNext: null, + _idlePrev: null, + _callback: callback, + _argv: null, + _onImmediate: callback, + domain: process.domain, + }; +} exports.setImmediate = function(callback, arg1, arg2, arg3) { if (typeof callback !== 'function') { @@ -567,41 +594,29 @@ exports.setImmediate = function(callback, arg1, arg2, arg3) { } var i, args; - var len = arguments.length; - var immediate = new Immediate(); + var immediate = createImmediate(callback); - L.init(immediate); - - switch (len) { + switch (arguments.length) { // fast cases case 0: case 1: - immediate._onImmediate = callback; break; case 2: - immediate._onImmediate = function() { - callback.call(immediate, arg1); - }; + immediate._argv = [arg1]; break; case 3: - immediate._onImmediate = function() { - callback.call(immediate, arg1, arg2); - }; + immediate._argv = [arg1, arg2]; break; case 4: - immediate._onImmediate = function() { - callback.call(immediate, arg1, arg2, arg3); - }; + immediate._argv = [arg1, arg2, arg3]; break; // slow case default: + var len = arguments.length; args = new Array(len - 1); for (i = 1; i < len; i++) args[i - 1] = arguments[i]; - - immediate._onImmediate = function() { - callback.apply(immediate, args); - }; + immediate._argv = args; break; } @@ -610,9 +625,6 @@ exports.setImmediate = function(callback, arg1, arg2, arg3) { process._immediateCallback = processImmediate; } - if (process.domain) - immediate.domain = process.domain; - L.append(immediateQueue, immediate); return immediate; From ad3bcb8fef0a286ee0ca1c5a6c8395e530b30d3b Mon Sep 17 00:00:00 2001 From: Andras Date: Tue, 26 Apr 2016 13:56:42 -0400 Subject: [PATCH 03/12] timers: 3x faster clearImmediate Instead of unlinking from the immediate queue immediate on clear, put off the unlink until processImmediate where it's more efficient. 3x faster clearImmediate processing The the benefits stack with the setImmediate speedups, ie total gain of 3.5x with 0 arguments and 4-5x with 1-3 args. Changed the code to defer unlinking from the immediate queue until processImmediate consumes the queue anyway. --- lib/timers.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/timers.js b/lib/timers.js index 6ed5221336cfca..925520a25d12fd 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -636,9 +636,6 @@ exports.clearImmediate = function(immediate) { immediate._onImmediate = undefined; - L.remove(immediate); - - if (L.isEmpty(immediateQueue)) { - process._needImmediateCallback = false; - } + // leave queued, much faster overall to skip it later in processImmediate + return; }; From 1f82a29455ae3188b9b50e1aeb4c3a1bb9374160 Mon Sep 17 00:00:00 2001 From: Andras Date: Wed, 27 Apr 2016 19:59:29 -0400 Subject: [PATCH 04/12] timers: setImmediate benchmarks Timings for sequential and concurren setImmediate with and without arguments, and set + clearImmediate. --- benchmark/timers/immediate.js | 113 ++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 benchmark/timers/immediate.js diff --git a/benchmark/timers/immediate.js b/benchmark/timers/immediate.js new file mode 100644 index 00000000000000..7d85dc0325a8be --- /dev/null +++ b/benchmark/timers/immediate.js @@ -0,0 +1,113 @@ +'use strict'; +var common = require('../common.js'); + +var bench = common.createBenchmark(main, { + thousands: [2000], + type: ['depth', 'depth1', 'breadth', 'breadth1', 'breadth4', 'clear'] +}); + +function main(conf) { + var N = +conf.thousands * 1e3; + switch (conf.type) { + case 'depth': + depth(N); + break; + case 'depth1': + depth1(N); + break; + case 'breadth': + breadth(N); + break; + case 'breadth1': + breadth1(N); + break; + case 'breadth4': + breadth4(N); + break; + case 'clear': + clear(N); + break; + } +} + +// setImmediate tail recursion, 0 arguments +function depth(N) { + var n = 0; + bench.start(); + setImmediate(cb); + function cb() { + n++; + if (n === N) + bench.end(N / 1e3); + else + setImmediate(cb); + } +} + +// setImmediate tail recursion, 1 argument +function depth1(N) { + var n = 0; + bench.start(); + setImmediate(cb, 1); + function cb(a1) { + n++; + if (n === N) + bench.end(N / 1e3); + else + setImmediate(cb, 1); + } +} + +// concurrent setImmediate, 0 arguments +function breadth(N) { + var n = 0; + bench.start(); + function cb() { + n++; + if (n === N) + bench.end(N / 1e3); + } + for (var i = 0; i < N; i++) { + setImmediate(cb); + } +} + +// concurrent setImmediate, 1 argument +function breadth1(N) { + var n = 0; + bench.start(); + function cb(a1) { + n++; + if (n === N) + bench.end(N / 1e3); + } + for (var i = 0; i < N; i++) { + setImmediate(cb, 1); + } +} + +// concurrent setImmediate, 4 arguments +function breadth4(N) { + var n = 0; + bench.start(); + function cb(a1, a2, a3, a4) { + n++; + if (n === N) + bench.end(N / 1e3); + } + for (var i = 0; i < N; i++) { + setImmediate(cb, 1, 2, 3, 4); + } +} + +function clear(N) { + bench.start(); + function cb(a1) { + if (a1 === 2) + bench.end(N / 1e3); + } + for (var i = 0; i < N; i++) { + clearImmediate(setImmediate(cb, 1)); + } + setImmediate(cb, 2); +} From 49b261120e299adcfe6e4be9df627d542ecc37ca Mon Sep 17 00:00:00 2001 From: Andras Date: Thu, 28 Apr 2016 11:22:29 -0400 Subject: [PATCH 05/12] timers: code review edits --- lib/internal/linkedlist.js | 2 +- lib/timers.js | 2 +- test/parallel/test-timers-linked-list.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/internal/linkedlist.js b/lib/internal/linkedlist.js index 33ada550efb62f..d50e3415aa4b54 100644 --- a/lib/internal/linkedlist.js +++ b/lib/internal/linkedlist.js @@ -8,7 +8,7 @@ exports.init = init; // create a new linked list function create() { - var list = { _idleNext: null, _idlePrev: null }; + const list = { _idleNext: null, _idlePrev: null }; init(list); return list; } diff --git a/lib/timers.js b/lib/timers.js index 925520a25d12fd..9d5b216e1136b6 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -594,7 +594,7 @@ exports.setImmediate = function(callback, arg1, arg2, arg3) { } var i, args; - var immediate = createImmediate(callback); + const immediate = createImmediate(callback); switch (arguments.length) { // fast cases diff --git a/test/parallel/test-timers-linked-list.js b/test/parallel/test-timers-linked-list.js index cdae64d344a303..4ec7770cfaa8ff 100644 --- a/test/parallel/test-timers-linked-list.js +++ b/test/parallel/test-timers-linked-list.js @@ -103,8 +103,8 @@ assert.equal(C, L.shift(list)); // list assert.ok(L.isEmpty(list)); -var list2 = L.create(); -var list3 = L.create(); +const list2 = L.create(); +const list3 = L.create(); assert.ok(L.isEmpty(list2)); assert.ok(L.isEmpty(list3)); assert.ok(list2 != list3); From c12ba47a73b096f386950040c527ba483d5ffd48 Mon Sep 17 00:00:00 2001 From: Andras Date: Thu, 28 Apr 2016 13:12:28 -0400 Subject: [PATCH 06/12] timers: code review edits (two-arg createImmediate) --- lib/timers.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/timers.js b/lib/timers.js index 9d5b216e1136b6..e7ebd38ca35c48 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -577,12 +577,12 @@ function runCallback(timer) { } -function createImmediate(callback) { +function createImmediate(callback, args) { return { _idleNext: null, _idlePrev: null, _callback: callback, - _argv: null, + _argv: args, _onImmediate: callback, domain: process.domain, }; @@ -594,7 +594,6 @@ exports.setImmediate = function(callback, arg1, arg2, arg3) { } var i, args; - const immediate = createImmediate(callback); switch (arguments.length) { // fast cases @@ -602,13 +601,13 @@ exports.setImmediate = function(callback, arg1, arg2, arg3) { case 1: break; case 2: - immediate._argv = [arg1]; + args = [arg1]; break; case 3: - immediate._argv = [arg1, arg2]; + args = [arg1, arg2]; break; case 4: - immediate._argv = [arg1, arg2, arg3]; + args = [arg1, arg2, arg3]; break; // slow case default: @@ -616,9 +615,9 @@ exports.setImmediate = function(callback, arg1, arg2, arg3) { args = new Array(len - 1); for (i = 1; i < len; i++) args[i - 1] = arguments[i]; - immediate._argv = args; break; } + var immediate = createImmediate(callback, args); if (!process._needImmediateCallback) { process._needImmediateCallback = true; From 6dcbd83772fff0ad3f75ac8bcdb889cd215e6c3d Mon Sep 17 00:00:00 2001 From: Andras Date: Fri, 29 Apr 2016 16:44:01 -0400 Subject: [PATCH 07/12] timers: code review edits --- lib/timers.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/timers.js b/lib/timers.js index e7ebd38ca35c48..ec547f803e2ef3 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -506,7 +506,7 @@ var immediateQueue = L.create(); function processImmediate() { - var queue = immediateQueue; + const queue = immediateQueue; var domain, immediate; immediateQueue = L.create(); @@ -558,8 +558,8 @@ function tryOnImmediate(immediate, queue) { } function runCallback(timer) { - var argv = timer._argv; - var argc = argv ? argv.length : 0; + const argv = timer._argv; + const argc = argv ? argv.length : 0; switch (argc) { // fast-path callbacks with 0-3 arguments case 0: From 197aac251bdd1c0b5cea2c0beff1e2af2ddf6e83 Mon Sep 17 00:00:00 2001 From: Andras Date: Sat, 30 Apr 2016 22:15:17 -0400 Subject: [PATCH 08/12] timers: create immediate objects with new Immediate() --- lib/timers.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/timers.js b/lib/timers.js index ec547f803e2ef3..08be7985f17a84 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -577,15 +577,15 @@ function runCallback(timer) { } -function createImmediate(callback, args) { - return { - _idleNext: null, - _idlePrev: null, - _callback: callback, - _argv: args, - _onImmediate: callback, - domain: process.domain, - }; +function Immediate() { + // assigning callback here in the constructor results in pessimal performance, + // so have caller annotate the object (node v6.0.0, v8 5.0.71.35) + this._idleNext = null; + this._idlePrev = null; + this._callback = null; + this._argv = null; + this._onImmediate = null; + this.domain = process.domain; } exports.setImmediate = function(callback, arg1, arg2, arg3) { @@ -617,7 +617,10 @@ exports.setImmediate = function(callback, arg1, arg2, arg3) { args[i - 1] = arguments[i]; break; } - var immediate = createImmediate(callback, args); + var immediate = new Immediate(); + immediate._callback = callback; + immediate._argv = args; + immediate._onImmediate = callback; if (!process._needImmediateCallback) { process._needImmediateCallback = true; From 4b54cd7762a1002531ee8bdb85ece2fe32319bef Mon Sep 17 00:00:00 2001 From: Andras Date: Sun, 1 May 2016 18:37:35 -0400 Subject: [PATCH 09/12] timers: speed up another 2x setImmediate() with > 3 args 220% faster yet setImmediate with 4 args, 240% with 5, 260% with 6 Changed the code to copy arguments into a dynamically extended array instead of preallocating the exact size. It seems f.apply(obj, args) runs much faster in node v6 if the args array was extended at run-time as opposed to preallocated the correct size. This behavior is new in node v6.0.0; v5.10.1 was 4x faster to apply an args array created the exact size. (Node v5 and v4 were the other way around, faster to .apply static sized arrays, and slower if dynamically grown.) // faster to copy args, but v6 slow to .apply them: var argv = new Array(arguments.length - 1); // exact size for (var i = 1; i < arguments.length; i++) argv[i - 1] = arguments[i]; // slower to copy args, but v6 much faster to .apply them: var argv = new Array(); // grow as needed for (var i = 1; i < arguments.length; i++) argv[i - 1] = arguments[i]; --- lib/timers.js | 7 +++---- test/parallel/test-timers-immediate.js | 6 ++++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/timers.js b/lib/timers.js index 08be7985f17a84..44b9ba06059a94 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -570,7 +570,7 @@ function runCallback(timer) { return timer._callback(argv[0], argv[1]); case 3: return timer._callback(argv[0], argv[1], argv[2]); - // more then 3 arguments run slower with .apply + // more than 3 arguments run slower with .apply default: return timer._callback.apply(timer, argv); } @@ -611,9 +611,8 @@ exports.setImmediate = function(callback, arg1, arg2, arg3) { break; // slow case default: - var len = arguments.length; - args = new Array(len - 1); - for (i = 1; i < len; i++) + args = [arg1, arg2, arg3]; + for (i = 4; i < arguments.length; i++) args[i - 1] = arguments[i]; break; } diff --git a/test/parallel/test-timers-immediate.js b/test/parallel/test-timers-immediate.js index cd0a423f4d963a..d160adc998fe1b 100644 --- a/test/parallel/test-timers-immediate.js +++ b/test/parallel/test-timers-immediate.js @@ -5,6 +5,7 @@ var assert = require('assert'); let immediateA = false; let immediateB = false; let immediateC = []; +let immediateD = []; setImmediate(function() { try { @@ -25,8 +26,13 @@ setImmediate(function(x, y, z) { immediateC = [x, y, z]; }, 1, 2, 3); +setImmediate(function(x, y, z, a, b) { + immediateD = [x, y, z, a, b]; +}, 1, 2, 3, 4, 5); + process.on('exit', function() { assert.ok(immediateA, 'Immediate should happen after normal execution'); assert.notStrictEqual(immediateB, true, 'immediateB should not fire'); assert.deepStrictEqual(immediateC, [1, 2, 3], 'immediateC args should match'); + assert.deepStrictEqual(immediateD, [1, 2, 3, 4, 5], '5 args should match'); }); From 8179d32bcea09e36e8c78852c4a65e345cb2a1c1 Mon Sep 17 00:00:00 2001 From: Andras Date: Fri, 6 May 2016 18:25:10 -0400 Subject: [PATCH 10/12] timers: code review edits --- lib/timers.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/timers.js b/lib/timers.js index 44b9ba06059a94..09c34d7b8e1b2a 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -638,5 +638,4 @@ exports.clearImmediate = function(immediate) { immediate._onImmediate = undefined; // leave queued, much faster overall to skip it later in processImmediate - return; }; From 77e0443d910ea4da3dfb35baf786600dd0774b73 Mon Sep 17 00:00:00 2001 From: Andras Date: Fri, 6 May 2016 18:43:29 -0400 Subject: [PATCH 11/12] timers: code review edits --- lib/timers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/timers.js b/lib/timers.js index 09c34d7b8e1b2a..142e7eeedb951a 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -578,7 +578,7 @@ function runCallback(timer) { function Immediate() { - // assigning callback here in the constructor results in pessimal performance, + // assigning the callback here can cause optimize/deoptimize thrashing // so have caller annotate the object (node v6.0.0, v8 5.0.71.35) this._idleNext = null; this._idlePrev = null; From 0e75f1f2785cf78643e12919a877948bf3e693a3 Mon Sep 17 00:00:00 2001 From: Andras Date: Sat, 7 May 2016 13:58:47 -0400 Subject: [PATCH 12/12] code review edits (comment less obvious parts of code) --- lib/timers.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/timers.js b/lib/timers.js index 142e7eeedb951a..1d50edca56876e 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -613,9 +613,11 @@ exports.setImmediate = function(callback, arg1, arg2, arg3) { default: args = [arg1, arg2, arg3]; for (i = 4; i < arguments.length; i++) + // extend array dynamically, makes .apply run much faster in v6.0.0 args[i - 1] = arguments[i]; break; } + // declaring it `const immediate` causes v6.0.0 to deoptimize this function var immediate = new Immediate(); immediate._callback = callback; immediate._argv = args;