Browse Source

timers: improve setTimeout/Interval performance

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: https://github.com/nodejs/node/pull/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>
v7.x
Brian White 8 years ago
committed by James M Snell
parent
commit
5e6035c080
  1. 180
      lib/timers.js
  2. 1
      test/message/timeout_throw.out

180
lib/timers.js

@ -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.
@ -128,21 +128,26 @@ function insert(item, unrefed) {
var list = lists[msecs];
if (!list) {
debug('no %d list was found in insert, creating a new one', msecs);
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.
list = new TimersList(msecs, unrefed);
const 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;
}
L.append(list, item);
assert(!L.isEmpty(list)); // list is not empty
return list;
}
function TimersList(msecs, unrefed) {
@ -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;
@ -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
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 createSingleTimeout(callback, after, args);
};
if (!(after >= 1 && after <= TIMEOUT_MAX)) {
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);
var length = arguments.length;
var ontimeout = callback;
switch (length) {
// fast cases
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:
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]);
callback.call(timer, args[0], args[1], args[2]);
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;
callback.apply(timer, args);
}
timer._onTimeout = ontimeout;
}
if (timer._repeat)
rearm(timer);
}
if (process.domain) timer.domain = process.domain;
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);
return timer;
};
}
}
const clearTimeout = exports.clearTimeout = function(timer) {
@ -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
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)
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];
ontimeout = () => callback.apply(timer, args);
break;
}
timer._onTimeout = wrapper;
timer._repeat = ontimeout;
if (process.domain) timer.domain = process.domain;
active(timer);
return timer;
return createRepeatTimeout(callback, repeat, args);
};
function wrapper() {
timer._repeat();
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
// Timer might be closed - no point in restarting it
if (!timer._repeat)
return;
var timer = new Timeout(repeat, callback, args);
timer._repeat = repeat;
if (process.domain)
timer.domain = process.domain;
// 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);
}
}
};
return timer;
}
exports.clearInterval = function(timer) {
if (timer && timer._repeat) {
@ -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();
}

1
test/message/timeout_throw.out

@ -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:*:*)

Loading…
Cancel
Save