Skip to content

Commit

Permalink
timers: improve linked list performance
Browse files Browse the repository at this point in the history
By moving the linked list manipulations out of a separate module and
into `lib/timers.js`, a significant performance increase is seen in the
timer benchamrks. I am seeing consistent 50% improvement with a p-value
of .0005 for the "breadth" benchmark with "thousands" set to 5.

This is probably due to removing the overhead of calling an external
function and possibly also removing some special handling that is not
needed in some cases.
  • Loading branch information
Trott committed Oct 7, 2016
1 parent 105e628 commit f8bb122
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 18 deletions.
2 changes: 1 addition & 1 deletion lib/internal/linkedlist.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ exports.create = create;

// show the most idle item
function peek(list) {
if (list._idlePrev == list) return null;
if (list._idlePrev === list) return null;
return list._idlePrev;
}
exports.peek = peek;
Expand Down
64 changes: 47 additions & 17 deletions lib/timers.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
'use strict';

const TimerWrap = process.binding('timer_wrap').Timer;
const L = require('internal/linkedlist');
const assert = require('assert');
const util = require('util');
const debug = util.debuglog('timer');
const kOnTimeout = TimerWrap.kOnTimeout | 0;
Expand Down Expand Up @@ -131,15 +129,28 @@ function insert(item, unrefed) {
lists[msecs] = list = createTimersList(msecs, unrefed);
}

L.append(list, item);
assert(!L.isEmpty(list)); // list is not empty
// put item at end of list
if (item._idleNext) {
item._idleNext._idlePrev = item._idlePrev;
}
if (item._idlePrev) {
item._idlePrev._idleNext = item._idleNext;
}

// items are linked with _idleNext -> (older) and _idlePrev -> (newer)
// TODO: swap the linkage to match the intuitive older items at "prev"
item._idleNext = list._idleNext;
item._idlePrev = list;

// the list _idleNext points to tail (newest) and _idlePrev to head (oldest)
list._idleNext._idlePrev = item;
list._idleNext = item;
}

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();
Expand All @@ -151,8 +162,8 @@ function createTimersList(msecs, unrefed) {
}

function TimersList(msecs, unrefed) {
this._idleNext = null; // Create the list with the linkedlist properties to
this._idlePrev = null; // prevent any unnecessary hidden class changes.
this._idleNext = this;
this._idlePrev = this;
this._timer = new TimerWrap();
this._unrefed = unrefed;
this.msecs = msecs;
Expand All @@ -168,7 +179,8 @@ function listOnTimeout() {
debug('now: %d', now);

var diff, timer;
while (timer = L.peek(list)) {
while (list._idlePrev !== list) {
timer = list._idlePrev;
diff = now - timer._idleStart;

// Check if this loop iteration is too early for the next timer.
Expand All @@ -185,8 +197,17 @@ function listOnTimeout() {

// The actual logic for when a timeout happens.

L.remove(timer);
assert(timer !== L.peek(list));
// Remove the timer from the linked list
if (timer._idleNext) {
timer._idleNext._idlePrev = timer._idlePrev;
}

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

timer._idleNext = null;
timer._idlePrev = null;

if (!timer._onTimeout) continue;

Expand All @@ -210,11 +231,9 @@ function listOnTimeout() {
domain.exit();
}

// If `L.peek(list)` returned nothing, the list was either empty or we have
// called all of the timer timeouts.
// As such, we can remove the list and clean up the TimerWrap C++ handle.
// All of the timer timeouts, if any, have been called.
// Remove the list and clean up the TimerWrap C++ handle.
debug('%d list empty', msecs);
assert(L.isEmpty(list));
this.close();

// Either refedLists[msecs] or unrefedLists[msecs] may have been removed and
Expand Down Expand Up @@ -263,11 +282,21 @@ function listOnTimeoutNT(list) {
// Re-using an existing handle allows us to skip that, so that a second `uv_run`
// will return no active handles, even when running `setTimeout(fn).unref()`.
function reuse(item) {
L.remove(item);
// Remove item from the linked list
if (item._idleNext) {
item._idleNext._idlePrev = item._idlePrev;
}

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

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

var list = refedLists[item._idleTimeout];
// if empty - reuse the watcher
if (list && L.isEmpty(list)) {
if (list && list._idleNext === list) {
debug('reuse hit');
list._timer.stop();
delete refedLists[item._idleTimeout];
Expand Down Expand Up @@ -313,7 +342,8 @@ exports.enroll = function(item, msecs) {
}

item._idleTimeout = msecs;
L.init(item);
item._idleNext = item;
item._idlePrev = item;
};


Expand Down

0 comments on commit f8bb122

Please sign in to comment.