Skip to content

Commit 0ed8839

Browse files
committed
timers: improve setImmediate() performance
This commit avoids re-creating a new immediate queue object every time the immediate queue is processed. Additionally, a few functions are tweaked to make them inlineable. These changes give ~6-7% boost in setImmediate() performance in the existing setImmediate() benchmarks. PR-URL: #8655 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Ilkka Myller <[email protected]> Reviewed-By: Jeremiah Senkpiel <[email protected]>
1 parent 1554735 commit 0ed8839

File tree

2 files changed

+88
-35
lines changed

2 files changed

+88
-35
lines changed

lib/internal/process/promises.js

+16-13
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,21 @@ function setupPromises(scheduleMicrotasks) {
4545
}
4646
}
4747

48+
function emitWarning(uid, reason) {
49+
const warning = new Error('Unhandled promise rejection ' +
50+
`(rejection id: ${uid}): ${reason}`);
51+
warning.name = 'UnhandledPromiseRejectionWarning';
52+
warning.id = uid;
53+
process.emitWarning(warning);
54+
if (!deprecationWarned) {
55+
deprecationWarned = true;
56+
process.emitWarning(
57+
'Unhandled promise rejections are deprecated. In the future, ' +
58+
'promise rejections that are not handled will terminate the ' +
59+
'Node.js process with a non-zero exit code.',
60+
'DeprecationWarning');
61+
}
62+
}
4863
var deprecationWarned = false;
4964
function emitPendingUnhandledRejections() {
5065
let hadListeners = false;
@@ -55,19 +70,7 @@ function setupPromises(scheduleMicrotasks) {
5570
hasBeenNotifiedProperty.set(promise, true);
5671
const uid = promiseToGuidProperty.get(promise);
5772
if (!process.emit('unhandledRejection', reason, promise)) {
58-
const warning = new Error('Unhandled promise rejection ' +
59-
`(rejection id: ${uid}): ${reason}`);
60-
warning.name = 'UnhandledPromiseRejectionWarning';
61-
warning.id = uid;
62-
process.emitWarning(warning);
63-
if (!deprecationWarned) {
64-
deprecationWarned = true;
65-
process.emitWarning(
66-
'Unhandled promise rejections are deprecated. In the future, ' +
67-
'promise rejections that are not handled will terminate the ' +
68-
'Node.js process with a non-zero exit code.',
69-
'DeprecationWarning');
70-
}
73+
emitWarning(uid, reason);
7174
} else {
7275
hadListeners = true;
7376
}

lib/timers.js

+72-22
Original file line numberDiff line numberDiff line change
@@ -514,17 +514,58 @@ Timeout.prototype.close = function() {
514514
};
515515

516516

517-
var immediateQueue = L.create();
517+
// A linked list for storing `setImmediate()` requests
518+
function ImmediateList() {
519+
this.head = null;
520+
this.tail = null;
521+
}
522+
523+
// Appends an item to the end of the linked list, adjusting the current tail's
524+
// previous and next pointers where applicable
525+
ImmediateList.prototype.append = function(item) {
526+
if (this.tail) {
527+
this.tail._idleNext = item;
528+
item._idlePrev = this.tail;
529+
} else {
530+
this.head = item;
531+
}
532+
this.tail = item;
533+
};
534+
535+
// Removes an item from the linked list, adjusting the pointers of adjacent
536+
// items and the linked list's head or tail pointers as necessary
537+
ImmediateList.prototype.remove = function(item) {
538+
if (item._idleNext) {
539+
item._idleNext._idlePrev = item._idlePrev;
540+
}
541+
542+
if (item._idlePrev) {
543+
item._idlePrev._idleNext = item._idleNext;
544+
}
545+
546+
if (item === this.head)
547+
this.head = item._idleNext;
548+
if (item === this.tail)
549+
this.tail = item._idlePrev;
550+
551+
item._idleNext = null;
552+
item._idlePrev = null;
553+
};
554+
555+
// Create a single linked list instance only once at startup
556+
var immediateQueue = new ImmediateList();
518557

519558

520559
function processImmediate() {
521-
const queue = immediateQueue;
522-
var domain, immediate;
560+
var immediate = immediateQueue.head;
561+
var tail = immediateQueue.tail;
562+
var domain;
523563

524-
immediateQueue = L.create();
564+
// Clear the linked list early in case new `setImmediate()` calls occur while
565+
// immediate callbacks are executed
566+
immediateQueue.head = immediateQueue.tail = null;
525567

526-
while (L.isEmpty(queue) === false) {
527-
immediate = L.shift(queue);
568+
while (immediate) {
528569
domain = immediate.domain;
529570

530571
if (!immediate._onImmediate)
@@ -534,36 +575,45 @@ function processImmediate() {
534575
domain.enter();
535576

536577
immediate._callback = immediate._onImmediate;
537-
tryOnImmediate(immediate, queue);
578+
tryOnImmediate(immediate, tail);
538579

539580
if (domain)
540581
domain.exit();
582+
583+
immediate = immediate._idleNext;
541584
}
542585

543586
// Only round-trip to C++ land if we have to. Calling clearImmediate() on an
544587
// immediate that's in |queue| is okay. Worst case is we make a superfluous
545588
// call to NeedImmediateCallbackSetter().
546-
if (L.isEmpty(immediateQueue)) {
589+
if (!immediateQueue.head) {
547590
process._needImmediateCallback = false;
548591
}
549592
}
550593

551594

552595
// An optimization so that the try/finally only de-optimizes (since at least v8
553596
// 4.7) what is in this smaller function.
554-
function tryOnImmediate(immediate, queue) {
597+
function tryOnImmediate(immediate, oldTail) {
555598
var threw = true;
556599
try {
557600
// make the actual call outside the try/catch to allow it to be optimized
558601
runCallback(immediate);
559602
threw = false;
560603
} finally {
561-
if (threw && !L.isEmpty(queue)) {
604+
if (threw && immediate._idleNext) {
562605
// Handle any remaining on next tick, assuming we're still alive to do so.
563-
while (!L.isEmpty(immediateQueue)) {
564-
L.append(queue, L.shift(immediateQueue));
606+
const curHead = immediateQueue.head;
607+
const next = immediate._idleNext;
608+
if (curHead) {
609+
curHead._idlePrev = oldTail;
610+
oldTail._idleNext = curHead;
611+
next._idlePrev = null;
612+
immediateQueue.head = next;
613+
} else {
614+
immediateQueue.head = next;
615+
immediateQueue.tail = oldTail;
565616
}
566-
immediateQueue = queue;
567617
process.nextTick(processImmediate);
568618
}
569619
}
@@ -617,17 +667,17 @@ exports.setImmediate = function(callback, arg1, arg2, arg3) {
617667
case 3:
618668
args = [arg1, arg2];
619669
break;
620-
case 4:
621-
args = [arg1, arg2, arg3];
622-
break;
623-
// slow case
624670
default:
625671
args = [arg1, arg2, arg3];
626672
for (i = 4; i < arguments.length; i++)
627673
// extend array dynamically, makes .apply run much faster in v6.0.0
628674
args[i - 1] = arguments[i];
629675
break;
630676
}
677+
return createImmediate(args, callback);
678+
};
679+
680+
function createImmediate(args, callback) {
631681
// declaring it `const immediate` causes v6.0.0 to deoptimize this function
632682
var immediate = new Immediate();
633683
immediate._callback = callback;
@@ -639,20 +689,20 @@ exports.setImmediate = function(callback, arg1, arg2, arg3) {
639689
process._immediateCallback = processImmediate;
640690
}
641691

642-
L.append(immediateQueue, immediate);
692+
immediateQueue.append(immediate);
643693

644694
return immediate;
645-
};
695+
}
646696

647697

648698
exports.clearImmediate = function(immediate) {
649699
if (!immediate) return;
650700

651-
immediate._onImmediate = undefined;
701+
immediate._onImmediate = null;
652702

653-
L.remove(immediate);
703+
immediateQueue.remove(immediate);
654704

655-
if (L.isEmpty(immediateQueue)) {
705+
if (!immediateQueue.head) {
656706
process._needImmediateCallback = false;
657707
}
658708
};

0 commit comments

Comments
 (0)