From 6f75b6672ca08c2bc3bf5fe14abb1a9648601e28 Mon Sep 17 00:00:00 2001 From: Andras Date: Wed, 27 Apr 2016 20:31:59 -0400 Subject: [PATCH] timers: optimize `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() - save the arguments and do not build closures for the callbacks PR-URL: https://github.com/nodejs/node/pull/6436 Reviewed-By: Benjamin Gruenbaum Reviewed-By: Jeremiah Senkpiel --- lib/internal/linkedlist.js | 2 +- lib/timers.js | 79 ++++++++++++++---------- test/parallel/test-timers-immediate.js | 6 ++ test/parallel/test-timers-linked-list.js | 4 +- 4 files changed, 56 insertions(+), 35 deletions(-) diff --git a/lib/internal/linkedlist.js b/lib/internal/linkedlist.js index 33ada550efb..d50e3415aa4 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 7ae25f6a325..9dbae32405c 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(); @@ -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) { + 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') { @@ -567,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; diff --git a/test/parallel/test-timers-immediate.js b/test/parallel/test-timers-immediate.js index cd0a423f4d9..d160adc998f 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 cdae64d344a..4ec7770cfaa 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);