Skip to content

Commit

Permalink
timers: improve setImmediate() performance
Browse files Browse the repository at this point in the history
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 <jasnell@gmail.com>
Reviewed-By: Ilkka Myller <ilkka.myller@nodefield.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
  • Loading branch information
mscdex committed Oct 5, 2016
1 parent 1554735 commit 0ed8839
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 35 deletions.
29 changes: 16 additions & 13 deletions lib/internal/process/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,21 @@ function setupPromises(scheduleMicrotasks) {
}
}

function emitWarning(uid, reason) {
const warning = new Error('Unhandled promise rejection ' +
`(rejection id: ${uid}): ${reason}`);
warning.name = 'UnhandledPromiseRejectionWarning';
warning.id = uid;
process.emitWarning(warning);
if (!deprecationWarned) {
deprecationWarned = true;
process.emitWarning(
'Unhandled promise rejections are deprecated. In the future, ' +
'promise rejections that are not handled will terminate the ' +
'Node.js process with a non-zero exit code.',
'DeprecationWarning');
}
}
var deprecationWarned = false;
function emitPendingUnhandledRejections() {
let hadListeners = false;
Expand All @@ -55,19 +70,7 @@ function setupPromises(scheduleMicrotasks) {
hasBeenNotifiedProperty.set(promise, true);
const uid = promiseToGuidProperty.get(promise);
if (!process.emit('unhandledRejection', reason, promise)) {
const warning = new Error('Unhandled promise rejection ' +
`(rejection id: ${uid}): ${reason}`);
warning.name = 'UnhandledPromiseRejectionWarning';
warning.id = uid;
process.emitWarning(warning);
if (!deprecationWarned) {
deprecationWarned = true;
process.emitWarning(
'Unhandled promise rejections are deprecated. In the future, ' +
'promise rejections that are not handled will terminate the ' +
'Node.js process with a non-zero exit code.',
'DeprecationWarning');
}
emitWarning(uid, reason);
} else {
hadListeners = true;
}
Expand Down
94 changes: 72 additions & 22 deletions lib/timers.js
Original file line number Diff line number Diff line change
Expand Up @@ -514,17 +514,58 @@ Timeout.prototype.close = function() {
};


var immediateQueue = L.create();
// A linked list for storing `setImmediate()` requests
function ImmediateList() {
this.head = null;
this.tail = null;
}

// Appends an item to the end of the linked list, adjusting the current tail's
// previous and next pointers where applicable
ImmediateList.prototype.append = function(item) {
if (this.tail) {
this.tail._idleNext = item;
item._idlePrev = this.tail;
} else {
this.head = item;
}
this.tail = item;
};

// Removes an item from the linked list, adjusting the pointers of adjacent
// items and the linked list's head or tail pointers as necessary
ImmediateList.prototype.remove = function(item) {
if (item._idleNext) {
item._idleNext._idlePrev = item._idlePrev;
}

if (item._idlePrev) {
item._idlePrev._idleNext = item._idleNext;
}

if (item === this.head)
this.head = item._idleNext;
if (item === this.tail)
this.tail = item._idlePrev;

item._idleNext = null;
item._idlePrev = null;
};

// Create a single linked list instance only once at startup
var immediateQueue = new ImmediateList();


function processImmediate() {
const queue = immediateQueue;
var domain, immediate;
var immediate = immediateQueue.head;
var tail = immediateQueue.tail;
var domain;

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

while (L.isEmpty(queue) === false) {
immediate = L.shift(queue);
while (immediate) {
domain = immediate.domain;

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

immediate._callback = immediate._onImmediate;
tryOnImmediate(immediate, queue);
tryOnImmediate(immediate, tail);

if (domain)
domain.exit();

immediate = immediate._idleNext;
}

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


// An optimization so that the try/finally only de-optimizes (since at least v8
// 4.7) what is in this smaller function.
function tryOnImmediate(immediate, queue) {
function tryOnImmediate(immediate, oldTail) {
var threw = true;
try {
// make the actual call outside the try/catch to allow it to be optimized
runCallback(immediate);
threw = false;
} finally {
if (threw && !L.isEmpty(queue)) {
if (threw && immediate._idleNext) {
// Handle any remaining on next tick, assuming we're still alive to do so.
while (!L.isEmpty(immediateQueue)) {
L.append(queue, L.shift(immediateQueue));
const curHead = immediateQueue.head;
const next = immediate._idleNext;
if (curHead) {
curHead._idlePrev = oldTail;
oldTail._idleNext = curHead;
next._idlePrev = null;
immediateQueue.head = next;
} else {
immediateQueue.head = next;
immediateQueue.tail = oldTail;
}
immediateQueue = queue;
process.nextTick(processImmediate);
}
}
Expand Down Expand Up @@ -617,17 +667,17 @@ exports.setImmediate = function(callback, arg1, arg2, arg3) {
case 3:
args = [arg1, arg2];
break;
case 4:
args = [arg1, arg2, arg3];
break;
// slow case
default:
args = [arg1, arg2, arg3];
for (i = 4; i < arguments.length; i++)
// extend array dynamically, makes .apply run much faster in v6.0.0
args[i - 1] = arguments[i];
break;
}
return createImmediate(args, callback);
};

function createImmediate(args, callback) {
// declaring it `const immediate` causes v6.0.0 to deoptimize this function
var immediate = new Immediate();
immediate._callback = callback;
Expand All @@ -639,20 +689,20 @@ exports.setImmediate = function(callback, arg1, arg2, arg3) {
process._immediateCallback = processImmediate;
}

L.append(immediateQueue, immediate);
immediateQueue.append(immediate);

return immediate;
};
}


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

immediate._onImmediate = undefined;
immediate._onImmediate = null;

L.remove(immediate);
immediateQueue.remove(immediate);

if (L.isEmpty(immediateQueue)) {
if (!immediateQueue.head) {
process._needImmediateCallback = false;
}
};

0 comments on commit 0ed8839

Please sign in to comment.