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 <benjamingr@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
This commit is contained in:
Andras 2016-04-27 20:31:59 -04:00 committed by Jeremiah Senkpiel
parent 6b0f86a8d5
commit 6f75b6672c
4 changed files with 56 additions and 35 deletions

View File

@ -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;
}

View File

@ -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;

View File

@ -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');
});

View File

@ -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);