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>
This commit is contained in:
parent
f5d997c476
commit
c8c2544cd9
206
lib/timers.js
206
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,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.
|
||||
@ -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');
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
function createSingleTimeout(callback, after, args) {
|
||||
after *= 1; // coalesce to number or NaN
|
||||
|
||||
if (!(after >= 1 && after <= TIMEOUT_MAX)) {
|
||||
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
|
||||
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;
|
||||
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) {
|
||||
@ -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');
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
function createRepeatTimeout(callback, repeat, args) {
|
||||
repeat *= 1; // coalesce to number or NaN
|
||||
|
||||
if (!(repeat >= 1 && repeat <= TIMEOUT_MAX)) {
|
||||
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) {
|
||||
@ -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();
|
||||
}
|
||||
|
@ -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…
x
Reference in New Issue
Block a user