Skip to content

Commit

Permalink
fix: reuse the iterator result of ReusableListIterator
Browse files Browse the repository at this point in the history
  • Loading branch information
twoeths committed Jul 29, 2024
1 parent 90a5ae6 commit e6c399e
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 3 deletions.
12 changes: 9 additions & 3 deletions packages/ssz/src/util/reusableListIterator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export class ReusableListIterator<T> implements ListIterator<T> {
private _length = 0;
private _totalLength = 0;
private pointer: LinkedNode<T> | null;
// this avoids memory allocation
private iteratorResult: IteratorResult<T>;

constructor() {
this.head = {
Expand All @@ -28,6 +30,7 @@ export class ReusableListIterator<T> implements ListIterator<T> {
};
this.tail = null;
this.pointer = null;
this.iteratorResult = {} as IteratorResult<T>;
}

get length(): number {
Expand All @@ -48,6 +51,7 @@ export class ReusableListIterator<T> implements ListIterator<T> {
this._length = 0;
// totalLength is not reset
this.pointer = null;
this.iteratorResult = {} as IteratorResult<T>;
}

/**
Expand Down Expand Up @@ -102,10 +106,12 @@ export class ReusableListIterator<T> implements ListIterator<T> {

// never yield value beyond the tail
const value = this.pointer.data;
const isNull = value === null;
this.pointer = this.pointer.next;

return isNull ? {done: true, value: undefined} : {done: false, value};
// should not allocate new object here
const isNull = value === null;
this.iteratorResult.done = isNull;
this.iteratorResult.value = isNull ? undefined: value;
return this.iteratorResult;
}

/**
Expand Down
50 changes: 50 additions & 0 deletions packages/ssz/test/perf/util/reusableListIterator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { itBench, setBenchOpts } from "@dapplion/benchmark";
import { ReusableListIterator } from "../../../src";

class A {
constructor(private readonly x: number, private readonly y: string, private readonly z: Uint8Array) {}
}

/**
* ReusableListIterator is 2x slower than using Array in this benchmark, however it does not allocate new array every time.
✓ ReusableListIterator 2000000 items 70.00170 ops/s 14.28537 ms/op - 105 runs 2.01 s
✓ Array 2000000 items 156.8627 ops/s 6.375003 ms/op - 114 runs 1.23 s
*/
describe("ReusableListIterator", function () {
setBenchOpts({
minRuns: 100
});

const pool = Array.from({length: 1024}, (_, i) => new A(i, String(i), Buffer.alloc(32, 1)));

const length = 2_000_000;
const list = new ReusableListIterator();
itBench({
id: `ReusableListIterator ${length} items`,
fn: () => {
// reusable, just reset
list.reset();
for (let i = 0; i < length; i++) {
list.push(pool[i % 1024]);
}
for (const a of list) {
a;
}
}
});

itBench({
id: `Array ${length} items`,
fn: () => {
// allocate every time
const arr = new Array<A>(length);
for (let i = 0; i < length; i++) {
arr[i] = pool[i % 1024]
}
for (const a of arr) {
a;
}
}
})
});

0 comments on commit e6c399e

Please sign in to comment.