Skip to content

Commit efb7a90

Browse files
AndrasFishrock123
Andras
authored andcommitted
timers: optimize setImmediate()
Save the setImmediate() callback arguments into an array instead of a closure, and invoke the callback on the arguments from an optimizable function. 60% faster setImmediate with 0 args (15% if self-recursive) 4x faster setImmediate with 1-3 args, 2x with > 3 seems to be faster with less memory pressure when memory is tight Changes: - use L.create() to build faster lists - use runCallback() from within tryOnImmediate() - save the arguments and do not build closures for the callbacks PR-URL: #6436 Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]>
1 parent a5d8945 commit efb7a90

File tree

4 files changed

+56
-35
lines changed

4 files changed

+56
-35
lines changed

lib/internal/linkedlist.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ exports.init = init;
88

99
// create a new linked list
1010
function create() {
11-
var list = { _idleNext: null, _idlePrev: null };
11+
const list = { _idleNext: null, _idlePrev: null };
1212
init(list);
1313
return list;
1414
}

lib/timers.js

+47-32
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ var immediateQueue = L.create();
506506

507507

508508
function processImmediate() {
509-
var queue = immediateQueue;
509+
const queue = immediateQueue;
510510
var domain, immediate;
511511

512512
immediateQueue = L.create();
@@ -515,9 +515,13 @@ function processImmediate() {
515515
immediate = L.shift(queue);
516516
domain = immediate.domain;
517517

518+
if (!immediate._onImmediate)
519+
continue;
520+
518521
if (domain)
519522
domain.enter();
520523

524+
immediate._callback = immediate._onImmediate;
521525
tryOnImmediate(immediate, queue);
522526

523527
if (domain)
@@ -538,7 +542,8 @@ function processImmediate() {
538542
function tryOnImmediate(immediate, queue) {
539543
var threw = true;
540544
try {
541-
immediate._onImmediate();
545+
// make the actual call outside the try/catch to allow it to be optimized
546+
runCallback(immediate);
542547
threw = false;
543548
} finally {
544549
if (threw && !L.isEmpty(queue)) {
@@ -552,67 +557,77 @@ function tryOnImmediate(immediate, queue) {
552557
}
553558
}
554559

560+
function runCallback(timer) {
561+
const argv = timer._argv;
562+
const argc = argv ? argv.length : 0;
563+
switch (argc) {
564+
// fast-path callbacks with 0-3 arguments
565+
case 0:
566+
return timer._callback();
567+
case 1:
568+
return timer._callback(argv[0]);
569+
case 2:
570+
return timer._callback(argv[0], argv[1]);
571+
case 3:
572+
return timer._callback(argv[0], argv[1], argv[2]);
573+
// more than 3 arguments run slower with .apply
574+
default:
575+
return timer._callback.apply(timer, argv);
576+
}
577+
}
555578

556-
function Immediate() { }
557-
558-
Immediate.prototype.domain = undefined;
559-
Immediate.prototype._onImmediate = undefined;
560-
Immediate.prototype._idleNext = undefined;
561-
Immediate.prototype._idlePrev = undefined;
562579

580+
function Immediate() {
581+
// assigning the callback here can cause optimize/deoptimize thrashing
582+
// so have caller annotate the object (node v6.0.0, v8 5.0.71.35)
583+
this._idleNext = null;
584+
this._idlePrev = null;
585+
this._callback = null;
586+
this._argv = null;
587+
this._onImmediate = null;
588+
this.domain = process.domain;
589+
}
563590

564591
exports.setImmediate = function(callback, arg1, arg2, arg3) {
565592
if (typeof callback !== 'function') {
566593
throw new TypeError('"callback" argument must be a function');
567594
}
568595

569596
var i, args;
570-
var len = arguments.length;
571-
var immediate = new Immediate();
572-
573-
L.init(immediate);
574597

575-
switch (len) {
598+
switch (arguments.length) {
576599
// fast cases
577600
case 0:
578601
case 1:
579-
immediate._onImmediate = callback;
580602
break;
581603
case 2:
582-
immediate._onImmediate = function() {
583-
callback.call(immediate, arg1);
584-
};
604+
args = [arg1];
585605
break;
586606
case 3:
587-
immediate._onImmediate = function() {
588-
callback.call(immediate, arg1, arg2);
589-
};
607+
args = [arg1, arg2];
590608
break;
591609
case 4:
592-
immediate._onImmediate = function() {
593-
callback.call(immediate, arg1, arg2, arg3);
594-
};
610+
args = [arg1, arg2, arg3];
595611
break;
596612
// slow case
597613
default:
598-
args = new Array(len - 1);
599-
for (i = 1; i < len; i++)
614+
args = [arg1, arg2, arg3];
615+
for (i = 4; i < arguments.length; i++)
616+
// extend array dynamically, makes .apply run much faster in v6.0.0
600617
args[i - 1] = arguments[i];
601-
602-
immediate._onImmediate = function() {
603-
callback.apply(immediate, args);
604-
};
605618
break;
606619
}
620+
// declaring it `const immediate` causes v6.0.0 to deoptimize this function
621+
var immediate = new Immediate();
622+
immediate._callback = callback;
623+
immediate._argv = args;
624+
immediate._onImmediate = callback;
607625

608626
if (!process._needImmediateCallback) {
609627
process._needImmediateCallback = true;
610628
process._immediateCallback = processImmediate;
611629
}
612630

613-
if (process.domain)
614-
immediate.domain = process.domain;
615-
616631
L.append(immediateQueue, immediate);
617632

618633
return immediate;

test/parallel/test-timers-immediate.js

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ var assert = require('assert');
55
let immediateA = false;
66
let immediateB = false;
77
let immediateC = [];
8+
let immediateD = [];
89

910
setImmediate(function() {
1011
try {
@@ -25,8 +26,13 @@ setImmediate(function(x, y, z) {
2526
immediateC = [x, y, z];
2627
}, 1, 2, 3);
2728

29+
setImmediate(function(x, y, z, a, b) {
30+
immediateD = [x, y, z, a, b];
31+
}, 1, 2, 3, 4, 5);
32+
2833
process.on('exit', function() {
2934
assert.ok(immediateA, 'Immediate should happen after normal execution');
3035
assert.notStrictEqual(immediateB, true, 'immediateB should not fire');
3136
assert.deepStrictEqual(immediateC, [1, 2, 3], 'immediateC args should match');
37+
assert.deepStrictEqual(immediateD, [1, 2, 3, 4, 5], '5 args should match');
3238
});

test/parallel/test-timers-linked-list.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ assert.equal(C, L.shift(list));
103103
// list
104104
assert.ok(L.isEmpty(list));
105105

106-
var list2 = L.create();
107-
var list3 = L.create();
106+
const list2 = L.create();
107+
const list3 = L.create();
108108
assert.ok(L.isEmpty(list2));
109109
assert.ok(L.isEmpty(list3));
110110
assert.ok(list2 != list3);

0 commit comments

Comments
 (0)