From e6c399e9e3ee5b6c0e446f5b2a1276da2725d865 Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Mon, 29 Jul 2024 17:47:21 +0700 Subject: [PATCH] fix: reuse the iterator result of ReusableListIterator --- packages/ssz/src/util/reusableListIterator.ts | 12 +++-- .../perf/util/reusableListIterator.test.ts | 50 +++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 packages/ssz/test/perf/util/reusableListIterator.test.ts diff --git a/packages/ssz/src/util/reusableListIterator.ts b/packages/ssz/src/util/reusableListIterator.ts index c4b32069..270bc58a 100644 --- a/packages/ssz/src/util/reusableListIterator.ts +++ b/packages/ssz/src/util/reusableListIterator.ts @@ -20,6 +20,8 @@ export class ReusableListIterator implements ListIterator { private _length = 0; private _totalLength = 0; private pointer: LinkedNode | null; + // this avoids memory allocation + private iteratorResult: IteratorResult; constructor() { this.head = { @@ -28,6 +30,7 @@ export class ReusableListIterator implements ListIterator { }; this.tail = null; this.pointer = null; + this.iteratorResult = {} as IteratorResult; } get length(): number { @@ -48,6 +51,7 @@ export class ReusableListIterator implements ListIterator { this._length = 0; // totalLength is not reset this.pointer = null; + this.iteratorResult = {} as IteratorResult; } /** @@ -102,10 +106,12 @@ export class ReusableListIterator implements ListIterator { // 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; } /** diff --git a/packages/ssz/test/perf/util/reusableListIterator.test.ts b/packages/ssz/test/perf/util/reusableListIterator.test.ts new file mode 100644 index 00000000..81f8ce60 --- /dev/null +++ b/packages/ssz/test/perf/util/reusableListIterator.test.ts @@ -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(length); + for (let i = 0; i < length; i++) { + arr[i] = pool[i % 1024] + } + for (const a of arr) { + a; + } + } + }) +}); +