Browse Source

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>
v6.x
Andras 9 years ago
committed by Jeremiah Senkpiel
parent
commit
efb7a90fa9
  1. 2
      lib/internal/linkedlist.js
  2. 79
      lib/timers.js
  3. 6
      test/parallel/test-timers-immediate.js
  4. 4
      test/parallel/test-timers-linked-list.js

2
lib/internal/linkedlist.js

@ -8,7 +8,7 @@ exports.init = init;
// create a new linked list // create a new linked list
function create() { function create() {
var list = { _idleNext: null, _idlePrev: null }; const list = { _idleNext: null, _idlePrev: null };
init(list); init(list);
return list; return list;
} }

79
lib/timers.js

@ -506,7 +506,7 @@ var immediateQueue = L.create();
function processImmediate() { function processImmediate() {
var queue = immediateQueue; const queue = immediateQueue;
var domain, immediate; var domain, immediate;
immediateQueue = L.create(); immediateQueue = L.create();
@ -515,9 +515,13 @@ function processImmediate() {
immediate = L.shift(queue); immediate = L.shift(queue);
domain = immediate.domain; domain = immediate.domain;
if (!immediate._onImmediate)
continue;
if (domain) if (domain)
domain.enter(); domain.enter();
immediate._callback = immediate._onImmediate;
tryOnImmediate(immediate, queue); tryOnImmediate(immediate, queue);
if (domain) if (domain)
@ -538,7 +542,8 @@ function processImmediate() {
function tryOnImmediate(immediate, queue) { function tryOnImmediate(immediate, queue) {
var threw = true; var threw = true;
try { try {
immediate._onImmediate(); // make the actual call outside the try/catch to allow it to be optimized
runCallback(immediate);
threw = false; threw = false;
} finally { } finally {
if (threw && !L.isEmpty(queue)) { 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) { exports.setImmediate = function(callback, arg1, arg2, arg3) {
if (typeof callback !== 'function') { if (typeof callback !== 'function') {
@ -567,52 +594,40 @@ exports.setImmediate = function(callback, arg1, arg2, arg3) {
} }
var i, args; var i, args;
var len = arguments.length;
var immediate = new Immediate();
L.init(immediate); switch (arguments.length) {
switch (len) {
// fast cases // fast cases
case 0: case 0:
case 1: case 1:
immediate._onImmediate = callback;
break; break;
case 2: case 2:
immediate._onImmediate = function() { args = [arg1];
callback.call(immediate, arg1);
};
break; break;
case 3: case 3:
immediate._onImmediate = function() { args = [arg1, arg2];
callback.call(immediate, arg1, arg2);
};
break; break;
case 4: case 4:
immediate._onImmediate = function() { args = [arg1, arg2, arg3];
callback.call(immediate, arg1, arg2, arg3);
};
break; break;
// slow case // slow case
default: default:
args = new Array(len - 1); args = [arg1, arg2, arg3];
for (i = 1; i < len; i++) for (i = 4; i < arguments.length; i++)
// extend array dynamically, makes .apply run much faster in v6.0.0
args[i - 1] = arguments[i]; args[i - 1] = arguments[i];
immediate._onImmediate = function() {
callback.apply(immediate, args);
};
break; 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) { if (!process._needImmediateCallback) {
process._needImmediateCallback = true; process._needImmediateCallback = true;
process._immediateCallback = processImmediate; process._immediateCallback = processImmediate;
} }
if (process.domain)
immediate.domain = process.domain;
L.append(immediateQueue, immediate); L.append(immediateQueue, immediate);
return immediate; return immediate;

6
test/parallel/test-timers-immediate.js

@ -5,6 +5,7 @@ var assert = require('assert');
let immediateA = false; let immediateA = false;
let immediateB = false; let immediateB = false;
let immediateC = []; let immediateC = [];
let immediateD = [];
setImmediate(function() { setImmediate(function() {
try { try {
@ -25,8 +26,13 @@ setImmediate(function(x, y, z) {
immediateC = [x, y, z]; immediateC = [x, y, z];
}, 1, 2, 3); }, 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() { process.on('exit', function() {
assert.ok(immediateA, 'Immediate should happen after normal execution'); assert.ok(immediateA, 'Immediate should happen after normal execution');
assert.notStrictEqual(immediateB, true, 'immediateB should not fire'); assert.notStrictEqual(immediateB, true, 'immediateB should not fire');
assert.deepStrictEqual(immediateC, [1, 2, 3], 'immediateC args should match'); assert.deepStrictEqual(immediateC, [1, 2, 3], 'immediateC args should match');
assert.deepStrictEqual(immediateD, [1, 2, 3, 4, 5], '5 args should match');
}); });

4
test/parallel/test-timers-linked-list.js

@ -103,8 +103,8 @@ assert.equal(C, L.shift(list));
// list // list
assert.ok(L.isEmpty(list)); assert.ok(L.isEmpty(list));
var list2 = L.create(); const list2 = L.create();
var list3 = L.create(); const list3 = L.create();
assert.ok(L.isEmpty(list2)); assert.ok(L.isEmpty(list2));
assert.ok(L.isEmpty(list3)); assert.ok(L.isEmpty(list3));
assert.ok(list2 != list3); assert.ok(list2 != list3);

Loading…
Cancel
Save