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. 206
      lib/timers.js
  2. 1
      test/message/timeout_throw.out

206
lib/timers.js

@ -94,8 +94,8 @@ const TIMEOUT_MAX = 2147483647; // 2^31-1
// //
// - key = time in milliseconds // - key = time in milliseconds
// - value = linked list // - value = linked list
const refedLists = {}; const refedLists = Object.create(null);
const unrefedLists = {}; const unrefedLists = Object.create(null);
// Schedule or re-schedule a timer. // Schedule or re-schedule a timer.
@ -128,23 +128,28 @@ function insert(item, unrefed) {
var list = lists[msecs]; var list = lists[msecs];
if (!list) { if (!list) {
debug('no %d list was found in insert, creating a new one', msecs); 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 lists[msecs] = list = createTimersList(msecs, unrefed);
// 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;
} }
L.append(list, item); L.append(list, item);
assert(!L.isEmpty(list)); // list is not empty 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) { function TimersList(msecs, unrefed) {
this._idleNext = null; // Create the list with the linkedlist properties to this._idleNext = null; // Create the list with the linkedlist properties to
this._idlePrev = null; // prevent any unnecessary hidden class changes. this._idlePrev = null; // prevent any unnecessary hidden class changes.
@ -229,7 +234,7 @@ function tryOnTimeout(timer, list) {
timer._called = true; timer._called = true;
var threw = true; var threw = true;
try { try {
timer._onTimeout(); ontimeout(timer);
threw = false; threw = false;
} finally { } finally {
if (!threw) return; 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') { if (typeof callback !== 'function') {
throw new TypeError('"callback" argument must be a function'); throw new TypeError('"callback" argument must be a function');
} }
after *= 1; // coalesce to number or NaN var len = arguments.length;
var args;
if (!(after >= 1 && after <= TIMEOUT_MAX)) { if (len === 3) {
after = 1; // schedule on next tick, follows browser behaviour 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); return createSingleTimeout(callback, after, args);
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;
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); active(timer);
return 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) { 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') { if (typeof callback !== 'function') {
throw new TypeError('"callback" argument must be a 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 repeat = 1; // schedule on next tick, follows browser behaviour
}
var timer = new Timeout(repeat); var timer = new Timeout(repeat, callback, args);
var length = arguments.length; timer._repeat = repeat;
var ontimeout = callback; if (process.domain)
switch (length) { timer.domain = process.domain;
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;
if (process.domain) timer.domain = process.domain;
active(timer); active(timer);
return 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) { exports.clearInterval = function(timer) {
if (timer && timer._repeat) { if (timer && timer._repeat) {
@ -445,19 +450,20 @@ exports.clearInterval = function(timer) {
}; };
function Timeout(after) { function Timeout(after, callback, args) {
this._called = false; this._called = false;
this._idleTimeout = after; this._idleTimeout = after;
this._idlePrev = this; this._idlePrev = this;
this._idleNext = this; this._idleNext = this;
this._idleStart = null; this._idleStart = null;
this._onTimeout = null; this._onTimeout = callback;
this._timerArgs = args;
this._repeat = null; this._repeat = null;
} }
function unrefdHandle() { function unrefdHandle() {
this.owner._onTimeout(); ontimeout(this.owner);
if (!this.owner._repeat) if (!this.owner._repeat)
this.owner.close(); this.owner.close();
} }

1
test/message/timeout_throw.out

@ -3,5 +3,6 @@
^ ^
ReferenceError: undefined_reference_error_maker is not defined ReferenceError: undefined_reference_error_maker is not defined
at Timeout._onTimeout (*test*message*timeout_throw.js:*:*) at Timeout._onTimeout (*test*message*timeout_throw.js:*:*)
at ontimeout (timers.js:*:*)
at tryOnTimeout (timers.js:*:*) at tryOnTimeout (timers.js:*:*)
at Timer.listOnTimeout (timers.js:*:*) at Timer.listOnTimeout (timers.js:*:*)

Loading…
Cancel
Save