Skip to content

Commit

Permalink
timers: improve setTimeout/Interval performance
Browse files Browse the repository at this point in the history
This commit improves timers performance by making functions
inlineable and avoiding the creation of extra closures/functions.

This commit also makes setTimeout/Interval argument handling
consistent with that of setImmediate.

These changes give ~22% improvement in the existing 'breadth' timers
benchmark.

PR-URL: #8661
Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ilkka Myller <ilkka.myller@nodefield.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
  • Loading branch information
mscdex authored and jasnell committed Oct 6, 2016
1 parent 23f7aec commit 5e6035c
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 100 deletions.
206 changes: 106 additions & 100 deletions lib/timers.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ const TIMEOUT_MAX = 2147483647; // 2^31-1
//
// - key = time in milliseconds
// - value = linked list
const refedLists = {};
const unrefedLists = {};
const refedLists = Object.create(null);
const unrefedLists = Object.create(null);


// Schedule or re-schedule a timer.
Expand Down Expand Up @@ -128,23 +128,28 @@ function insert(item, unrefed) {
var list = lists[msecs];
if (!list) {
debug('no %d list was found in insert, creating a new one', msecs);
// Make a new linked list of timers, and create a TimerWrap to schedule
// processing for the list.
list = new TimersList(msecs, unrefed);
L.init(list);
list._timer._list = list;

if (unrefed === true) list._timer.unref();
list._timer.start(msecs);

lists[msecs] = list;
list._timer[kOnTimeout] = listOnTimeout;
lists[msecs] = list = createTimersList(msecs, unrefed);
}

L.append(list, item);
assert(!L.isEmpty(list)); // list is not empty
}

function createTimersList(msecs, unrefed) {
// Make a new linked list of timers, and create a TimerWrap to schedule
// processing for the list.
const list = new TimersList(msecs, unrefed);
L.init(list);
list._timer._list = list;

if (unrefed === true) list._timer.unref();
list._timer.start(msecs);

list._timer[kOnTimeout] = listOnTimeout;

return list;
}

function TimersList(msecs, unrefed) {
this._idleNext = null; // Create the list with the linkedlist properties to
this._idlePrev = null; // prevent any unnecessary hidden class changes.
Expand Down Expand Up @@ -229,7 +234,7 @@ function tryOnTimeout(timer, list) {
timer._called = true;
var threw = true;
try {
timer._onTimeout();
ontimeout(timer);
threw = false;
} finally {
if (!threw) return;
Expand Down Expand Up @@ -317,51 +322,76 @@ exports.enroll = function(item, msecs) {
*/


exports.setTimeout = function(callback, after) {
exports.setTimeout = function(callback, after, arg1, arg2, arg3) {
if (typeof callback !== 'function') {
throw new TypeError('"callback" argument must be a function');
}

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

if (!(after >= 1 && after <= TIMEOUT_MAX)) {
after = 1; // schedule on next tick, follows browser behaviour
var len = arguments.length;
var args;
if (len === 3) {
args = [arg1];
} else if (len === 4) {
args = [arg1, arg2];
} else if (len > 4) {
args = [arg1, arg2, arg3];
for (var i = 5; i < len; i++)
// extend array dynamically, makes .apply run much faster in v6.0.0
args[i - 2] = arguments[i];
}

var timer = new Timeout(after);
var length = arguments.length;
var ontimeout = callback;
switch (length) {
// fast cases
case 1:
case 2:
break;
case 3:
ontimeout = () => callback.call(timer, arguments[2]);
break;
case 4:
ontimeout = () => callback.call(timer, arguments[2], arguments[3]);
break;
case 5:
ontimeout =
() => callback.call(timer, arguments[2], arguments[3], arguments[4]);
break;
// slow case
default:
var args = new Array(length - 2);
for (var i = 2; i < length; i++)
args[i - 2] = arguments[i];
ontimeout = () => callback.apply(timer, args);
break;
}
timer._onTimeout = ontimeout;
return createSingleTimeout(callback, after, args);
};

if (process.domain) timer.domain = process.domain;
function createSingleTimeout(callback, after, args) {
after *= 1; // coalesce to number or NaN
if (!(after >= 1 && after <= TIMEOUT_MAX))
after = 1; // schedule on next tick, follows browser behaviour

var timer = new Timeout(after, callback, args);
if (process.domain)
timer.domain = process.domain;

active(timer);

return timer;
};
}


function ontimeout(timer) {
var args = timer._timerArgs;
var callback = timer._onTimeout;
if (!args)
callback.call(timer);
else {
switch (args.length) {
case 1:
callback.call(timer, args[0]);
break;
case 2:
callback.call(timer, args[0], args[1]);
break;
case 3:
callback.call(timer, args[0], args[1], args[2]);
break;
default:
callback.apply(timer, args);
}
}
if (timer._repeat)
rearm(timer);
}


function rearm(timer) {
// If timer is unref'd (or was - it's permanently removed from the list.)
if (timer._handle && timer instanceof Timeout) {
timer._handle.start(timer._repeat);
} else {
timer._idleTimeout = timer._repeat;
active(timer);
}
}


const clearTimeout = exports.clearTimeout = function(timer) {
Expand All @@ -376,66 +406,41 @@ const clearTimeout = exports.clearTimeout = function(timer) {
};


exports.setInterval = function(callback, repeat) {
exports.setInterval = function(callback, repeat, arg1, arg2, arg3) {
if (typeof callback !== 'function') {
throw new TypeError('"callback" argument must be a function');
}

repeat *= 1; // coalesce to number or NaN
var len = arguments.length;
var args;
if (len === 3) {
args = [arg1];
} else if (len === 4) {
args = [arg1, arg2];
} else if (len > 4) {
args = [arg1, arg2, arg3];
for (var i = 5; i < len; i++)
// extend array dynamically, makes .apply run much faster in v6.0.0
args[i - 2] = arguments[i];
}

return createRepeatTimeout(callback, repeat, args);
};

if (!(repeat >= 1 && repeat <= TIMEOUT_MAX)) {
function createRepeatTimeout(callback, repeat, args) {
repeat *= 1; // coalesce to number or NaN
if (!(repeat >= 1 && repeat <= TIMEOUT_MAX))
repeat = 1; // schedule on next tick, follows browser behaviour
}

var timer = new Timeout(repeat);
var length = arguments.length;
var ontimeout = callback;
switch (length) {
case 1:
case 2:
break;
case 3:
ontimeout = () => callback.call(timer, arguments[2]);
break;
case 4:
ontimeout = () => callback.call(timer, arguments[2], arguments[3]);
break;
case 5:
ontimeout =
() => callback.call(timer, arguments[2], arguments[3], arguments[4]);
break;
default:
var args = new Array(length - 2);
for (var i = 2; i < length; i += 1)
args[i - 2] = arguments[i];
ontimeout = () => callback.apply(timer, args);
break;
}
timer._onTimeout = wrapper;
timer._repeat = ontimeout;
var timer = new Timeout(repeat, callback, args);
timer._repeat = repeat;
if (process.domain)
timer.domain = process.domain;

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

return timer;

function wrapper() {
timer._repeat();

// Timer might be closed - no point in restarting it
if (!timer._repeat)
return;

// If timer is unref'd (or was - it's permanently removed from the list.)
if (this._handle) {
this._handle.start(repeat);
} else {
timer._idleTimeout = repeat;
active(timer);
}
}
};

}

exports.clearInterval = function(timer) {
if (timer && timer._repeat) {
Expand All @@ -445,19 +450,20 @@ exports.clearInterval = function(timer) {
};


function Timeout(after) {
function Timeout(after, callback, args) {
this._called = false;
this._idleTimeout = after;
this._idlePrev = this;
this._idleNext = this;
this._idleStart = null;
this._onTimeout = null;
this._onTimeout = callback;
this._timerArgs = args;
this._repeat = null;
}


function unrefdHandle() {
this.owner._onTimeout();
ontimeout(this.owner);
if (!this.owner._repeat)
this.owner.close();
}
Expand Down
1 change: 1 addition & 0 deletions test/message/timeout_throw.out
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
^
ReferenceError: undefined_reference_error_maker is not defined
at Timeout._onTimeout (*test*message*timeout_throw.js:*:*)
at ontimeout (timers.js:*:*)
at tryOnTimeout (timers.js:*:*)
at Timer.listOnTimeout (timers.js:*:*)

0 comments on commit 5e6035c

Please sign in to comment.