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
|
// - 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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
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
|
after = 1; // schedule on next tick, follows browser behaviour
|
||||||
}
|
|
||||||
|
|
||||||
var timer = new Timeout(after);
|
var timer = new Timeout(after, callback, args);
|
||||||
var length = arguments.length;
|
if (process.domain)
|
||||||
var ontimeout = callback;
|
timer.domain = process.domain;
|
||||||
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;
|
|
||||||
|
|
||||||
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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
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
|
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();
|
||||||
}
|
}
|
||||||
|
@ -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…
x
Reference in New Issue
Block a user