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); +} diff --git a/lib/internal/linkedlist.js b/lib/internal/linkedlist.js index 02186cfedcb9f6..d50e3415aa4b54 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() { + const 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..1d50edca56876e 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -502,24 +502,26 @@ Timeout.prototype.close = function() { }; -var immediateQueue = {}; -L.init(immediateQueue); +var immediateQueue = L.create(); function processImmediate() { - var queue = immediateQueue; + const queue = immediateQueue; var domain, immediate; - immediateQueue = {}; - L.init(immediateQueue); + immediateQueue = L.create(); while (L.isEmpty(queue) === false) { immediate = L.shift(queue); domain = immediate.domain; + if (!immediate._onImmediate) + continue; + if (domain) domain.enter(); + immediate._callback = immediate._onImmediate; tryOnImmediate(immediate, queue); if (domain) @@ -540,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)) { @@ -554,14 +557,36 @@ function tryOnImmediate(immediate, queue) { } } +function runCallback(timer) { + const argv = timer._argv; + const 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 than 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 Immediate() { + // 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; + this._callback = null; + this._argv = null; + this._onImmediate = null; + this.domain = process.domain; +} exports.setImmediate = function(callback, arg1, arg2, arg3) { if (typeof callback !== 'function') { @@ -569,52 +594,40 @@ exports.setImmediate = function(callback, arg1, arg2, arg3) { } var i, args; - var len = arguments.length; - var immediate = new Immediate(); - - 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); - }; + args = [arg1]; break; case 3: - immediate._onImmediate = function() { - callback.call(immediate, arg1, arg2); - }; + args = [arg1, arg2]; break; case 4: - immediate._onImmediate = function() { - callback.call(immediate, arg1, arg2, arg3); - }; + args = [arg1, arg2, arg3]; break; // slow case default: - args = new Array(len - 1); - for (i = 1; i < len; i++) + 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]; - - immediate._onImmediate = function() { - callback.apply(immediate, args); - }; break; } + // declaring it `const immediate` causes v6.0.0 to deoptimize this function + var immediate = new Immediate(); + immediate._callback = callback; + immediate._argv = args; + immediate._onImmediate = callback; if (!process._needImmediateCallback) { process._needImmediateCallback = true; process._immediateCallback = processImmediate; } - if (process.domain) - immediate.domain = process.domain; - L.append(immediateQueue, immediate); return immediate; @@ -626,9 +639,5 @@ 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 }; 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'); }); diff --git a/test/parallel/test-timers-linked-list.js b/test/parallel/test-timers-linked-list.js index b5ff9f56bf0700..4ec7770cfaa8ff 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)); +const list2 = L.create(); +const list3 = L.create(); +assert.ok(L.isEmpty(list2)); +assert.ok(L.isEmpty(list3)); +assert.ok(list2 != list3);