Skip to content

Commit

Permalink
timer: Improve performance of callbacks
Browse files Browse the repository at this point in the history
setImmediate, setTimeout, and setInterval were called in an inefficient
way, especially in the presence of arguments.  This optimization
improves their performance, with special cases for up to 4 arguments.
Performance of setImmediate increases with 35%, setInterval with 60%,
setTimeout with 70%.

PR-URL: #406
Reviewed-by: Trevor Norris <trev.norris@gmail.com>
Reviewed-by: Christian Tellnes <christian@tellnes.com>
  • Loading branch information
RubenVerborgh authored and trevnorris committed Mar 4, 2015
1 parent 6190a22 commit bd1bd7e
Showing 1 changed file with 97 additions and 33 deletions.
130 changes: 97 additions & 33 deletions lib/timers.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,9 @@ exports.active = function(item) {
*/


exports.setTimeout = function(callback, after) {
var timer;
exports.setTimeout = function(callback, after, arg1, arg2, arg3) {
var timer, i, args;
var len = arguments.length;

after *= 1; // coalesce to number or NaN

Expand All @@ -184,22 +185,38 @@ exports.setTimeout = function(callback, after) {

timer = new Timeout(after);

if (arguments.length <= 2) {
timer._onTimeout = callback;
} else {
/*
* Sometimes setTimeout is called with arguments, EG
*
* setTimeout(callback, 2000, "hello", "world")
*
* If that's the case we need to call the callback with
* those args. The overhead of an extra closure is not
* desired in the normal case.
*/
var args = Array.prototype.slice.call(arguments, 2);
timer._onTimeout = function() {
callback.apply(timer, args);
};
switch (len) {
// fast cases
case 0:
case 1:
case 2:
timer._onTimeout = callback;
break;
case 3:
timer._onTimeout = function() {
callback.call(timer, arg1);
};
break;
case 4:
timer._onTimeout = function() {
callback.call(timer, arg1, arg2);
};
break;
case 5:
timer._onTimeout = function() {
callback.call(timer, arg1, arg2, arg3);
};
break;
// slow case
default:
args = new Array(len - 2);
for (i = 2; i < len; i++)
args[i - 2] = arguments[i];

timer._onTimeout = function() {
callback.apply(timer, args);
};
break;
}

if (process.domain) timer.domain = process.domain;
Expand All @@ -222,25 +239,50 @@ exports.clearTimeout = function(timer) {
};


exports.setInterval = function(callback, repeat) {
exports.setInterval = function(callback, repeat, arg1, arg2, arg3) {
repeat *= 1; // coalesce to number or NaN

if (!(repeat >= 1 && repeat <= TIMEOUT_MAX)) {
repeat = 1; // schedule on next tick, follows browser behaviour
}

var args, i;
var timer = new Timeout(repeat);
var args = Array.prototype.slice.call(arguments, 2);
var len = arguments.length - 2;
timer._onTimeout = wrapper;
timer._repeat = true;
// Initialize args once for repeated invocation of slow case below
if (len > 3) {
args = new Array(len);
for (i = 0; i < len; i++)
args[i] = arguments[i + 2];
}

if (process.domain) timer.domain = process.domain;
exports.active(timer);

return timer;

function wrapper() {
callback.apply(this, args);
switch (len) {
// fast cases
case 0:
callback.call(this);
break;
case 1:
callback.call(this, arg1);
break;
case 2:
callback.call(this, arg1, arg2);
break;
case 3:
callback.call(this, arg1, arg2, arg3);
break;
// slow case
default:
callback.apply(this, args);
break;
}
// If callback called clearInterval().
if (timer._repeat === false) return;
// If timer is unref'd (or was - it's permanently removed from the list.)
Expand Down Expand Up @@ -361,22 +403,44 @@ Immediate.prototype._idleNext = undefined;
Immediate.prototype._idlePrev = undefined;


exports.setImmediate = function(callback) {
exports.setImmediate = function(callback, arg1, arg2, arg3) {
var i, args;
var len = arguments.length;
var immediate = new Immediate();
var args, index;

L.init(immediate);

immediate._onImmediate = callback;

if (arguments.length > 1) {
args = [];
for (index = 1; index < arguments.length; index++)
args.push(arguments[index]);

immediate._onImmediate = function() {
callback.apply(immediate, args);
};
switch (len) {
// fast cases
case 0:
case 1:
immediate._onImmediate = callback;
break;
case 2:
immediate._onImmediate = function() {
callback.call(immediate, arg1);
};
break;
case 3:
immediate._onImmediate = function() {
callback.call(immediate, arg1, arg2);
};
break;
case 4:
immediate._onImmediate = function() {
callback.call(immediate, arg1, arg2, arg3);
};
break;
// slow case
default:
args = new Array(len - 1);
for (i = 1; i < len; i++)
args[i - 1] = arguments[i];

immediate._onImmediate = function() {
callback.apply(immediate, args);
};
break;
}

if (!process._needImmediateCallback) {
Expand Down

0 comments on commit bd1bd7e

Please sign in to comment.