diff --git a/lib/queue.js b/lib/queue.js new file mode 100644 index 0000000..ab2b3a4 --- /dev/null +++ b/lib/queue.js @@ -0,0 +1,37 @@ +'use strict'; + +class Queue { + constructor() { + this.elements = []; + this.offset = 0; + } + + push(...element) { + this.elements.push(...element); + return this; + } + + shift() { + if (this.length === 0) return null; + const first = this.elements[this.offset]; + this.offset++; + + if (this.offset * 2 < this.elements.length) return first; + + this.elements = this.elements.slice(this.offset); + this.offset = 0; + return first; + } + + get length() { + return this.elements.length - this.offset; + } + + *[Symbol.iterator]() { + for (let i = this.offset; i < this.length; i++) { + yield this.elements[i]; + } + } +} + +module.exports = Queue; diff --git a/package.json b/package.json index fc12466..cb9a3a8 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "email": "timur.shemsedinov@gmail.com" }, "main": "web-locks.js", - "files": [], + "files": ["lib/queue.js"], "engines": { "node": ">=11.0.0" }, diff --git a/test.js b/test.js index 8f48423..9ee171e 100644 --- a/test.js +++ b/test.js @@ -8,6 +8,7 @@ const tests = [ 'deadlock', 'recursive-deadlock', 'thread-main', + 'queue-performance', ]; (async () => { diff --git a/test/queue-performance.js b/test/queue-performance.js new file mode 100644 index 0000000..daa66e4 --- /dev/null +++ b/test/queue-performance.js @@ -0,0 +1,36 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { performance } = require('node:perf_hooks'); +const Queue = require('../lib/queue.js'); + +const simpleQueueBench = (QueueClass) => { + const queue = new QueueClass(); + const iterations = 100000; + let sum = 0; + + const timeStart = performance.now(); + for (let i = 0; i < iterations; i++) { + queue.push(() => i); + } + for (let i = 0; i < iterations; i++) { + sum += queue.shift()(); + } + const timeEnd = performance.now(); + + const execTime = timeEnd - timeStart; + return { sum, execTime }; +}; + +module.exports = async () => { + const testedClasses = [Array, Queue]; + + const [arrayResult, queueResult] = testedClasses.map(simpleQueueBench); + const { sum: arraySum, execTime: arrayExecTime } = arrayResult; + const { sum: queueSum, execTime: queueExecTime } = queueResult; + + const isQueueFaster = arrayExecTime > queueExecTime; + + assert.strictEqual(queueSum, arraySum); + assert.strictEqual(isQueueFaster, true); +}; diff --git a/web-locks.js b/web-locks.js index 7283d33..2f4d87b 100644 --- a/web-locks.js +++ b/web-locks.js @@ -1,6 +1,7 @@ 'use strict'; const { EventEmitter } = require('events'); +const Queue = require('./lib/queue.js'); const threads = require('worker_threads'); const { isMainThread, parentPort } = threads; const isWorkerThread = !isMainThread; @@ -14,7 +15,7 @@ class Lock { constructor(name, mode = 'exclusive', buffer = null) { this.name = name; this.mode = mode; // 'exclusive' or 'shared' - this.queue = []; + this.queue = new Queue(); this.owner = false; this.trying = false; this.buffer = buffer ? buffer : new SharedArrayBuffer(4); @@ -57,8 +58,8 @@ class Lock { class LockManagerSnapshot { constructor(resources) { - const held = []; - const pending = []; + const held = new Queue(); + const pending = new Queue(); this.held = held; this.pending = pending;