Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions benchmark/util/fixed-queue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict';

const common = require('../common');

const bench = common.createBenchmark(main, {
n: [1e5],
}, { flags: ['--expose-internals'] });

function main({ n }) {
const FixedQueue = require('internal/fixed_queue');
const queue = new FixedQueue();
bench.start();
for (let i = 0; i < n; i++)
queue.push(i);
for (let i = 0; i < n; i++)
queue.shift();
bench.end(n);
}
38 changes: 27 additions & 11 deletions lib/internal/fixed_queue.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ const kMask = kSize - 1;
// | undefined | | item |
// +-----------+ +-----------+
//
// Implementation detail: To reduce allocations, a single spare
// FixedCircularBuffer may be kept for reuse. When a segment empties, it
// is stored as the spare, and when the head fills, the spare is linked
// in. This does not change the active list structure or queue semantics.
//
// Adding a value means moving `top` forward by one, removing means
// moving `bottom` forward by one. After reaching the end, the queue
// wraps around.
Expand All @@ -74,36 +79,46 @@ class FixedCircularBuffer {
}

push(data) {
this.list[this.top] = data;
this.top = (this.top + 1) & kMask;
const top = this.top;
this.list[top] = data;
this.top = (top + 1) & kMask;
}

shift() {
const nextItem = this.list[this.bottom];
const bottom = this.bottom;
const nextItem = this.list[bottom];
if (nextItem === undefined)
return null;
this.list[this.bottom] = undefined;
this.bottom = (this.bottom + 1) & kMask;
this.list[bottom] = undefined;
this.bottom = (bottom + 1) & kMask;
return nextItem;
}
}

module.exports = class FixedQueue {
constructor() {
this.head = this.tail = new FixedCircularBuffer();
this._spare = null;
}

isEmpty() {
return this.head.isEmpty();
}

push(data) {
if (this.head.isFull()) {
// Head is full: Creates a new queue, sets the old queue's `.next` to it,
// and sets it as the new main queue.
this.head = this.head.next = new FixedCircularBuffer();
let head = this.head;
if (head.isFull()) {
// Head is full: reuse the spare buffer if available.
// Otherwise create a new one. Link it and advance head.
let nextBuf = this._spare;
if (nextBuf !== null) {
this._spare = null;
} else {
nextBuf = new FixedCircularBuffer();
}
this.head = head = head.next = nextBuf;
}
this.head.push(data);
head.push(data);
}

shift() {
Expand All @@ -112,7 +127,8 @@ module.exports = class FixedQueue {
if (tail.isEmpty() && tail.next !== null) {
// If there is another queue, it forms the new tail.
this.tail = tail.next;
tail.next = null;
// Overwrite any existing spare
this._spare = tail;
}
return next;
}
Expand Down
Loading