Skip to content

Commit 5e6035c

Browse files
mscdexjasnell
authored andcommitted
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: #8661 Reviewed-By: Franziska Hinkelmann <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Ilkka Myller <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]>
1 parent 23f7aec commit 5e6035c

File tree

2 files changed

+107
-100
lines changed

2 files changed

+107
-100
lines changed

lib/timers.js

Lines changed: 106 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ const TIMEOUT_MAX = 2147483647; // 2^31-1
9494
//
9595
// - key = time in milliseconds
9696
// - value = linked list
97-
const refedLists = {};
98-
const unrefedLists = {};
97+
const refedLists = Object.create(null);
98+
const unrefedLists = Object.create(null);
9999

100100

101101
// Schedule or re-schedule a timer.
@@ -128,23 +128,28 @@ function insert(item, unrefed) {
128128
var list = lists[msecs];
129129
if (!list) {
130130
debug('no %d list was found in insert, creating a new one', msecs);
131-
// Make a new linked list of timers, and create a TimerWrap to schedule
132-
// processing for the list.
133-
list = new TimersList(msecs, unrefed);
134-
L.init(list);
135-
list._timer._list = list;
136-
137-
if (unrefed === true) list._timer.unref();
138-
list._timer.start(msecs);
139-
140-
lists[msecs] = list;
141-
list._timer[kOnTimeout] = listOnTimeout;
131+
lists[msecs] = list = createTimersList(msecs, unrefed);
142132
}
143133

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

138+
function createTimersList(msecs, unrefed) {
139+
// Make a new linked list of timers, and create a TimerWrap to schedule
140+
// processing for the list.
141+
const list = new TimersList(msecs, unrefed);
142+
L.init(list);
143+
list._timer._list = list;
144+
145+
if (unrefed === true) list._timer.unref();
146+
list._timer.start(msecs);
147+
148+
list._timer[kOnTimeout] = listOnTimeout;
149+
150+
return list;
151+
}
152+
148153
function TimersList(msecs, unrefed) {
149154
this._idleNext = null; // Create the list with the linkedlist properties to
150155
this._idlePrev = null; // prevent any unnecessary hidden class changes.
@@ -229,7 +234,7 @@ function tryOnTimeout(timer, list) {
229234
timer._called = true;
230235
var threw = true;
231236
try {
232-
timer._onTimeout();
237+
ontimeout(timer);
233238
threw = false;
234239
} finally {
235240
if (!threw) return;
@@ -317,51 +322,76 @@ exports.enroll = function(item, msecs) {
317322
*/
318323

319324

320-
exports.setTimeout = function(callback, after) {
325+
exports.setTimeout = function(callback, after, arg1, arg2, arg3) {
321326
if (typeof callback !== 'function') {
322327
throw new TypeError('"callback" argument must be a function');
323328
}
324329

325-
after *= 1; // coalesce to number or NaN
326-
327-
if (!(after >= 1 && after <= TIMEOUT_MAX)) {
328-
after = 1; // schedule on next tick, follows browser behaviour
330+
var len = arguments.length;
331+
var args;
332+
if (len === 3) {
333+
args = [arg1];
334+
} else if (len === 4) {
335+
args = [arg1, arg2];
336+
} else if (len > 4) {
337+
args = [arg1, arg2, arg3];
338+
for (var i = 5; i < len; i++)
339+
// extend array dynamically, makes .apply run much faster in v6.0.0
340+
args[i - 2] = arguments[i];
329341
}
330342

331-
var timer = new Timeout(after);
332-
var length = arguments.length;
333-
var ontimeout = callback;
334-
switch (length) {
335-
// fast cases
336-
case 1:
337-
case 2:
338-
break;
339-
case 3:
340-
ontimeout = () => callback.call(timer, arguments[2]);
341-
break;
342-
case 4:
343-
ontimeout = () => callback.call(timer, arguments[2], arguments[3]);
344-
break;
345-
case 5:
346-
ontimeout =
347-
() => callback.call(timer, arguments[2], arguments[3], arguments[4]);
348-
break;
349-
// slow case
350-
default:
351-
var args = new Array(length - 2);
352-
for (var i = 2; i < length; i++)
353-
args[i - 2] = arguments[i];
354-
ontimeout = () => callback.apply(timer, args);
355-
break;
356-
}
357-
timer._onTimeout = ontimeout;
343+
return createSingleTimeout(callback, after, args);
344+
};
358345

359-
if (process.domain) timer.domain = process.domain;
346+
function createSingleTimeout(callback, after, args) {
347+
after *= 1; // coalesce to number or NaN
348+
if (!(after >= 1 && after <= TIMEOUT_MAX))
349+
after = 1; // schedule on next tick, follows browser behaviour
350+
351+
var timer = new Timeout(after, callback, args);
352+
if (process.domain)
353+
timer.domain = process.domain;
360354

361355
active(timer);
362356

363357
return timer;
364-
};
358+
}
359+
360+
361+
function ontimeout(timer) {
362+
var args = timer._timerArgs;
363+
var callback = timer._onTimeout;
364+
if (!args)
365+
callback.call(timer);
366+
else {
367+
switch (args.length) {
368+
case 1:
369+
callback.call(timer, args[0]);
370+
break;
371+
case 2:
372+
callback.call(timer, args[0], args[1]);
373+
break;
374+
case 3:
375+
callback.call(timer, args[0], args[1], args[2]);
376+
break;
377+
default:
378+
callback.apply(timer, args);
379+
}
380+
}
381+
if (timer._repeat)
382+
rearm(timer);
383+
}
384+
385+
386+
function rearm(timer) {
387+
// If timer is unref'd (or was - it's permanently removed from the list.)
388+
if (timer._handle && timer instanceof Timeout) {
389+
timer._handle.start(timer._repeat);
390+
} else {
391+
timer._idleTimeout = timer._repeat;
392+
active(timer);
393+
}
394+
}
365395

366396

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

378408

379-
exports.setInterval = function(callback, repeat) {
409+
exports.setInterval = function(callback, repeat, arg1, arg2, arg3) {
380410
if (typeof callback !== 'function') {
381411
throw new TypeError('"callback" argument must be a function');
382412
}
383413

384-
repeat *= 1; // coalesce to number or NaN
414+
var len = arguments.length;
415+
var args;
416+
if (len === 3) {
417+
args = [arg1];
418+
} else if (len === 4) {
419+
args = [arg1, arg2];
420+
} else if (len > 4) {
421+
args = [arg1, arg2, arg3];
422+
for (var i = 5; i < len; i++)
423+
// extend array dynamically, makes .apply run much faster in v6.0.0
424+
args[i - 2] = arguments[i];
425+
}
426+
427+
return createRepeatTimeout(callback, repeat, args);
428+
};
385429

386-
if (!(repeat >= 1 && repeat <= TIMEOUT_MAX)) {
430+
function createRepeatTimeout(callback, repeat, args) {
431+
repeat *= 1; // coalesce to number or NaN
432+
if (!(repeat >= 1 && repeat <= TIMEOUT_MAX))
387433
repeat = 1; // schedule on next tick, follows browser behaviour
388-
}
389434

390-
var timer = new Timeout(repeat);
391-
var length = arguments.length;
392-
var ontimeout = callback;
393-
switch (length) {
394-
case 1:
395-
case 2:
396-
break;
397-
case 3:
398-
ontimeout = () => callback.call(timer, arguments[2]);
399-
break;
400-
case 4:
401-
ontimeout = () => callback.call(timer, arguments[2], arguments[3]);
402-
break;
403-
case 5:
404-
ontimeout =
405-
() => callback.call(timer, arguments[2], arguments[3], arguments[4]);
406-
break;
407-
default:
408-
var args = new Array(length - 2);
409-
for (var i = 2; i < length; i += 1)
410-
args[i - 2] = arguments[i];
411-
ontimeout = () => callback.apply(timer, args);
412-
break;
413-
}
414-
timer._onTimeout = wrapper;
415-
timer._repeat = ontimeout;
435+
var timer = new Timeout(repeat, callback, args);
436+
timer._repeat = repeat;
437+
if (process.domain)
438+
timer.domain = process.domain;
416439

417-
if (process.domain) timer.domain = process.domain;
418440
active(timer);
419441

420442
return timer;
421-
422-
function wrapper() {
423-
timer._repeat();
424-
425-
// Timer might be closed - no point in restarting it
426-
if (!timer._repeat)
427-
return;
428-
429-
// If timer is unref'd (or was - it's permanently removed from the list.)
430-
if (this._handle) {
431-
this._handle.start(repeat);
432-
} else {
433-
timer._idleTimeout = repeat;
434-
active(timer);
435-
}
436-
}
437-
};
438-
443+
}
439444

440445
exports.clearInterval = function(timer) {
441446
if (timer && timer._repeat) {
@@ -445,19 +450,20 @@ exports.clearInterval = function(timer) {
445450
};
446451

447452

448-
function Timeout(after) {
453+
function Timeout(after, callback, args) {
449454
this._called = false;
450455
this._idleTimeout = after;
451456
this._idlePrev = this;
452457
this._idleNext = this;
453458
this._idleStart = null;
454-
this._onTimeout = null;
459+
this._onTimeout = callback;
460+
this._timerArgs = args;
455461
this._repeat = null;
456462
}
457463

458464

459465
function unrefdHandle() {
460-
this.owner._onTimeout();
466+
ontimeout(this.owner);
461467
if (!this.owner._repeat)
462468
this.owner.close();
463469
}

test/message/timeout_throw.out

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
^
44
ReferenceError: undefined_reference_error_maker is not defined
55
at Timeout._onTimeout (*test*message*timeout_throw.js:*:*)
6+
at ontimeout (timers.js:*:*)
67
at tryOnTimeout (timers.js:*:*)
78
at Timer.listOnTimeout (timers.js:*:*)

0 commit comments

Comments
 (0)