Skip to content

Commit

Permalink
Merge pull request #6 from kleinron/fix-DynamicCyclicQueue-dequeue-re…
Browse files Browse the repository at this point in the history
…size

Fix dynamic cyclic queue dequeue resize
  • Loading branch information
kleinron authored Jan 14, 2023
2 parents 7336760 + 21a7fea commit 7d48c41
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 18 deletions.
22 changes: 10 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,6 @@ However, even with this optimization, the memory footprint of `LinkedQueue` is t
### Linked List of Ring Buffers
A ring buffer, or a cyclic queue, is a *bounded* data structure that relies on an array. It's very fast, but bounded.
We can, however, introduce a new data structure named `ChunkedQueue`, in which we create a `LinkedQueue` with each item in it to be a cyclic queue.
**It's not a generic queue**, as it has a weakness, which is out of the scope of this page, so use with caution.

### DynamicCyclicQueue
Same as a cyclic queue, but can exceed the initial length of the underlying array.
Expand All @@ -168,22 +167,21 @@ Remove best and worst results (in terms of ops/sec), and take the average (mean)
Note: we took a very large value for P, otherwise complexity related issues won't come up.

## Results
| Class Name | Ops/Sec | RAM used (MB) |
|:-------------------|----------:|--------------:|
| DynamicArrayQueue | **5** | 8 |
| ChunkedQueue | 28307 | **28** |
| DynamicCyclicQueue | **44864** | 102 |
| LinkedQueue | 25815 | 143 |
| Class Name | Ops/Sec | RAM used (MB) |
|:-------------------|--------:|--------------:|
| DynamicArrayQueue | **5** | 8 |
| ChunkedQueue | 31800 | **28** |
| DynamicCyclicQueue | 27100 | 102 |
| LinkedQueue | 29800 | 143 |

## Analysis
1. The naive implementation, `DynamicArrayQueue`, is so slow that it can't be considered as an option
2. The fastest implementation is `DynamicCyclicQueue`, and has an average RAM usage
3. The default implementation of `ChunkedQueue` has the lowest RAM usage, with the second-fastest measure of ops/sec
4. The common `LinkedQueue` implementation is not the fastest one, even with *O(1)* time complexity, and it's the most wasteful in terms of RAM usage
2. The fastest implementation is `ChunkedQueue`, and has the lowest RAM usage
3. The common `LinkedQueue` implementation is not the fastest one, even with *O(1)* time complexity, and it's the most wasteful in terms of RAM usage
4. Classes `DynamicCyclicQueue` and `LinkedQueue` have quite similar results: the former has a lower RAM usage and the latter performs a bit better.

## Suggestions
* Use the provided `DynamicCyclicQueue` for a generic solution
* For some cases, e.g. telemetry shipping, `ChunkedQueue` is better - very low memory footprint
* Use the provided `ChunkedQueue` for a generic solution

## License
MIT © Ron Klein
4 changes: 2 additions & 2 deletions benchmark/report-config.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"operations": [3000000, 500000, 100000, 500000, 100000, 500000, 100000, 500000, 100000, 500000, 100000, 500000, 100000, 500000],
"classes": ["ChunkedQueue", "ChunkedQueue-1024", "DynamicCyclicQueue", "LinkedQueue"],
"iterationsPerClass": 20,
"classes": ["ChunkedQueue", "ChunkedQueue-128", "DynamicCyclicQueue", "LinkedQueue"],
"iterationsPerClass": 200,
"shuffle": true,
"removeMinMax": true
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lite-fifo",
"version": "0.3.2",
"version": "0.3.3",
"license": "MIT",
"main": "src/index.js",
"types": "types/index.d.ts",
Expand Down
6 changes: 3 additions & 3 deletions src/DynamicCyclicQueue.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,15 +167,15 @@ class DynamicCyclicQueue {
* @returns {void}
*/
_reduceIfNeeded () {
if (this._size <= 1) {
if (this._size <= MIN_INITIAL_CAPACITY) {
return;
}

// check if current size is 1/3 or less of allocated array
if (((this._size << 1) + this._size) <= this._arr.length) {
// remove 1/3 of the allocation
this._normalizeToZeroIndex();
const reduceCount = this._arr.length - ((this._size << 1) + this._size);
// re-allocate so that new capacity is (size * 2)
const reduceCount = this._arr.length - (this._size << 1);
for (let i = 0; i < reduceCount; i++) {
this._arr.pop();
}
Expand Down
23 changes: 23 additions & 0 deletions test/src/commonTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,29 @@ describe('Common API for all implementations', () => {
});

if (stressTestEnabled && name !== 'DynamicArrayQueue') {
describe(name, () => {
it('exhaust api', () => {
const counts = [3000000, 500000, 100000, 500000, 100000, 500000, 100000, 500000, 100000, 500000, 100000, 500000, 100000, 500000];
const queue = new DynamicCyclicQueue();
let opIndex = 1;
let value = 0;
for (let i = 0; i < counts.length; i++) {
const count = counts[i];
opIndex = 1 - opIndex;
if (opIndex === 0) { // enqueue
for (let i = 0; i < count; i++) {
queue.enqueue(++value);
}
} else {
for (let i = 0; i < count; i++) {
queue.dequeue();
}
}
}
assert.equal(queue.size(), 100000);
});
});

describe('random enqueue and dequeue actions', () => {
for (let i = 0; i < stressTestCount; i++) {
it(`${name} iteration #${i}`, () => {
Expand Down

0 comments on commit 7d48c41

Please sign in to comment.