From 89ed8e28d03662a06def3eb3f5369302720c57cc Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Wed, 24 Jul 2024 15:01:10 +0700 Subject: [PATCH 1/6] feat: implement ReusableListIterator --- .../src/hashComputation.ts | 5 +- packages/ssz/src/interface.ts | 6 + packages/ssz/src/util/reusableListIterator.ts | 139 ++++++++++++++++++ packages/ssz/src/viewDU/arrayComposite.ts | 29 ++++ .../unit/util/reusableListIterator.test.ts | 64 ++++++++ 5 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 packages/ssz/src/util/reusableListIterator.ts create mode 100644 packages/ssz/test/unit/util/reusableListIterator.test.ts diff --git a/packages/persistent-merkle-tree/src/hashComputation.ts b/packages/persistent-merkle-tree/src/hashComputation.ts index d7f48a25..d6a8c7ab 100644 --- a/packages/persistent-merkle-tree/src/hashComputation.ts +++ b/packages/persistent-merkle-tree/src/hashComputation.ts @@ -55,7 +55,10 @@ export class HashComputationLevel { * run before every run */ reset(): void { - // keep this.head + // keep this.head object, only release the data + this.head.src0 = null as unknown as Node; + this.head.src1 = null as unknown as Node; + this.head.dest = null as unknown as Node; this.tail = null; this._length = 0; // totalLength is not reset diff --git a/packages/ssz/src/interface.ts b/packages/ssz/src/interface.ts index 240c2795..05240a04 100644 --- a/packages/ssz/src/interface.ts +++ b/packages/ssz/src/interface.ts @@ -16,6 +16,12 @@ export interface List extends ArrayLike { pop(): T | undefined; } +export interface ListIterator { + readonly length: number; + push(...values: T[]): void; + [Symbol.iterator](): Iterator; +} + export type Container> = T; export type ByteVector = Vector; diff --git a/packages/ssz/src/util/reusableListIterator.ts b/packages/ssz/src/util/reusableListIterator.ts new file mode 100644 index 00000000..c4b32069 --- /dev/null +++ b/packages/ssz/src/util/reusableListIterator.ts @@ -0,0 +1,139 @@ +import {ListIterator} from "../interface"; + +class LinkedNode { + data: T; + next: LinkedNode | null = null; + + constructor(data: T) { + this.data = data; + } +} + +/** + * A LinkedList that's designed to be reused overtime. + * Before every run, reset() should be called. + * After every run, clean() should be called. + */ +export class ReusableListIterator implements ListIterator { + private head: LinkedNode; + private tail: LinkedNode | null; + private _length = 0; + private _totalLength = 0; + private pointer: LinkedNode | null; + + constructor() { + this.head = { + data: null as unknown as T, + next: null, + }; + this.tail = null; + this.pointer = null; + } + + get length(): number { + return this._length; + } + + get totalLength(): number { + return this._totalLength; + } + + /** + * run before every run + */ + reset(): void { + // keep this.head object, only release the data + this.head.data = null as unknown as T; + this.tail = null; + this._length = 0; + // totalLength is not reset + this.pointer = null; + } + + /** + * Append new data to the tail + * This will overwrite the existing data if it is not null, or grow the list if needed. + */ + push(value: T): void { + if (this.tail !== null) { + let newTail = this.tail.next; + if (newTail !== null) { + newTail.data = value; + } else { + // grow the list + newTail = {data: value, next: null}; + this.tail.next = newTail; + this._totalLength++; + } + this.tail = newTail; + this._length++; + return; + } + + // first item + this.head.data = value; + this.tail = this.head; + this._length = 1; + if (this._totalLength === 0) { + this._totalLength = 1; + } + // else _totalLength > 0, do not set + } + + /** + * run after every run + * hashComps may still refer to the old Nodes, we should release them to avoid memory leak. + */ + clean(): void { + let node = this.tail?.next ?? null; + while (node !== null && node.data !== null) { + node.data = null as unknown as T; + node = node.next; + } + } + + /** + * Implement Iterator for this class + */ + next(): IteratorResult { + if (!this.pointer || this.tail === null) { + return {done: true, value: undefined}; + } + + // 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}; + } + + /** + * This is convenient method to consume HashComputationLevel with for-of loop + * See "next" method above for the actual implementation + */ + [Symbol.iterator](): IterableIterator { + this.pointer = this.head; + return this; + } + + toArray(): T[] { + const result: T[] = []; + for (const data of this) { + result.push(data); + } + return result; + } + + /** + * For testing only + */ + dump(): T[] { + const result: T[] = []; + let node: LinkedNode | null = this.head; + for (; node !== null; node = node.next) { + result.push(node.data); + } + return result; + } +} diff --git a/packages/ssz/src/viewDU/arrayComposite.ts b/packages/ssz/src/viewDU/arrayComposite.ts index 4c429f18..f420798c 100644 --- a/packages/ssz/src/viewDU/arrayComposite.ts +++ b/packages/ssz/src/viewDU/arrayComposite.ts @@ -10,6 +10,7 @@ import {ValueOf} from "../type/abstract"; import {CompositeType, CompositeView, CompositeViewDU} from "../type/composite"; import {ArrayCompositeType} from "../view/arrayComposite"; import {TreeViewDU} from "./abstract"; +import {ListIterator} from "../interface"; export type ArrayCompositeTreeViewDUCache = { nodes: Node[]; @@ -160,6 +161,20 @@ export class ArrayCompositeTreeViewDU< return views; } + /** + * Similar to getAllReadonly but support returning a LiaIterator that could be reused + */ + getAllReadonlyIter(views?: ListIterator>): ListIterator> { + this.populateAllNodes(); + + views = views ?? new Array>(this._length); + for (let i = 0; i < this._length; i++) { + const view = this.type.elementType.getViewDU(this.nodes[i], this.caches[i]); + views.push(view); + } + return views; + } + /** * WARNING: Returns all commited changes, if there are any pending changes commit them beforehand */ @@ -176,6 +191,20 @@ export class ArrayCompositeTreeViewDU< return values; } + /** + * WARNING: Returns all commited changes, if there are any pending changes commit them beforehand + */ + getAllReadonlyValuesIter(values?: ListIterator>): ListIterator> { + this.populateAllNodes(); + + values = values ?? new Array>(this._length); + for (let i = 0; i < this._length; i++) { + const value = this.type.elementType.tree_toValue(this.nodes[i]); + values.push(value); + } + return values; + } + /** * When we need to compute HashComputations (hcByLevel != null): * - if old _rootNode is hashed, then only need to put pending changes to hcByLevel diff --git a/packages/ssz/test/unit/util/reusableListIterator.test.ts b/packages/ssz/test/unit/util/reusableListIterator.test.ts new file mode 100644 index 00000000..8af8140e --- /dev/null +++ b/packages/ssz/test/unit/util/reusableListIterator.test.ts @@ -0,0 +1,64 @@ +import {expect} from "chai"; +import {ReusableListIterator} from "../../../src/util/reusableListIterator"; + +describe("ReusableListIterator", () => { + let list: ReusableListIterator; + + beforeEach(() => { + list = new ReusableListIterator(); + list.push(0); + }); + + it("should reset", () => { + list.reset(); + expect(list.length).to.be.equal(0); + expect(list.totalLength).to.be.equal(1); + expect(list.toArray()).to.be.deep.equal([]); + }); + + it("should push", () => { + list.push(1); + expect(list.length).to.be.equal(2); + expect(list.totalLength).to.be.equal(2); + const arr = list.toArray(); + expect(arr.length).to.be.equal(2); + expect(arr).to.be.deep.equal([0, 1]); + }); + + it("reset then push full", () => { + list.push(1); + list.reset(); + list.push(1); + list.push(2); + list.clean(); + expect(list.length).to.be.equal(2); + expect(list.totalLength).to.be.equal(2); + const arr = list.toArray(); + expect(arr).to.be.deep.equal([1, 2]); + }); + + it("reset then push partial", () => { + list.push(1); + // totalLength = 2 now + list.reset(); + list.push(1); + list.clean(); + expect(list.length).to.be.equal(1); + expect(list.totalLength).to.be.equal(2); + const arr = list.toArray(); + expect(arr).to.be.deep.equal([1]); + }); + + it("clean", () => { + list.push(1); + list.reset(); + list.push(1); + list.clean(); + expect(list.length).to.be.equal(1); + expect(list.totalLength).to.be.equal(2); + const arr = list.toArray(); + expect(arr).to.be.deep.equal([1]); + const all = list.dump(); + expect(all).to.be.deep.equal([1, null]); + }); +}); From 90a5ae606539464ca2732c6e05472795a3986d51 Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Wed, 24 Jul 2024 15:13:26 +0700 Subject: [PATCH 2/6] fix: export ReusableListIterator --- packages/ssz/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ssz/src/index.ts b/packages/ssz/src/index.ts index c140c60c..f2379e17 100644 --- a/packages/ssz/src/index.ts +++ b/packages/ssz/src/index.ts @@ -37,6 +37,7 @@ export {BitArray, getUint8ByteToBitBooleanArray} from "./value/bitArray"; // Utils export {fromHexString, toHexString, byteArrayEquals} from "./util/byteArray"; +export {ReusableListIterator} from "./util/reusableListIterator"; export {hash64, symbolCachedPermanentRoot} from "./util/merkleize"; From e6c399e9e3ee5b6c0e446f5b2a1276da2725d865 Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Mon, 29 Jul 2024 17:47:21 +0700 Subject: [PATCH 3/6] 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; + } + } + }) +}); + From 99edbedb84ef764501ef128402ffc0763d04570f Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Wed, 31 Jul 2024 11:10:10 +0700 Subject: [PATCH 4/6] chore: update ReusableListIterator benchmark --- packages/ssz/src/util/reusableListIterator.ts | 4 +- .../perf/util/reusableListIterator.test.ts | 40 ++++++++----------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/packages/ssz/src/util/reusableListIterator.ts b/packages/ssz/src/util/reusableListIterator.ts index 270bc58a..7e1c35a7 100644 --- a/packages/ssz/src/util/reusableListIterator.ts +++ b/packages/ssz/src/util/reusableListIterator.ts @@ -51,7 +51,7 @@ export class ReusableListIterator implements ListIterator { this._length = 0; // totalLength is not reset this.pointer = null; - this.iteratorResult = {} as IteratorResult; + // no need to reset iteratorResult } /** @@ -110,7 +110,7 @@ export class ReusableListIterator implements ListIterator { // should not allocate new object here const isNull = value === null; this.iteratorResult.done = isNull; - this.iteratorResult.value = isNull ? undefined: value; + 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 index 81f8ce60..4417077e 100644 --- a/packages/ssz/test/perf/util/reusableListIterator.test.ts +++ b/packages/ssz/test/perf/util/reusableListIterator.test.ts @@ -1,50 +1,44 @@ -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) {} -} +import {itBench} from "@dapplion/benchmark"; +import {ReusableListIterator} from "../../../src"; +import {Validator} from "../../lodestarTypes/phase0"; +import {getValidator} from "../../utils/generateEth2Objs"; /** - * 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 + * This test create validator object every time intentionally, this mimics an environment where there are a lot of memory allocation. + * On average, Array is very fast, however it's pretty expensive to allocate a big array and it may cause a spike due to gc randomly. + * ReusableListIterator is faster in average and it's more stable due to no memory allocation involved. + * ReusableListIterator + ✓ ReusableListIterator 2000000 items 0.5724982 ops/s 1.746731 s/op - 13 runs 24.1 s + ✓ Array 2000000 items 0.4655988 ops/s 2.147772 s/op - 14 runs 32.0 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(); + 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]); + list.push(getValidator(i)); } for (const a of list) { a; } - } + }, }); itBench({ id: `Array ${length} items`, fn: () => { // allocate every time - const arr = new Array(length); + const arr = new Array(length); for (let i = 0; i < length; i++) { - arr[i] = pool[i % 1024] + arr[i] = getValidator(i); } for (const a of arr) { a; } - } - }) + }, + }); }); - From 570fd435f393ca05234f8da190739ae699fd1489 Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Wed, 31 Jul 2024 15:16:26 +0700 Subject: [PATCH 5/6] feat: implement get*Iter() apis for view/arrayComposite.ts --- packages/ssz/src/view/arrayComposite.ts | 32 +++++++++++++ packages/ssz/src/viewDU/arrayComposite.ts | 10 ++-- .../test/perf/byType/listComposite.test.ts | 48 +++++++++++++++---- packages/ssz/test/perf/iterate.test.ts | 20 +++++++- 4 files changed, 94 insertions(+), 16 deletions(-) diff --git a/packages/ssz/src/view/arrayComposite.ts b/packages/ssz/src/view/arrayComposite.ts index 0e9ae75a..97168645 100644 --- a/packages/ssz/src/view/arrayComposite.ts +++ b/packages/ssz/src/view/arrayComposite.ts @@ -2,6 +2,7 @@ import {getNodesAtDepth, HashComputationLevel, Node, toGindexBitstring, Tree} fr import {ValueOf} from "../type/abstract"; import {CompositeType, CompositeView, CompositeViewDU} from "../type/composite"; import {TreeView} from "./abstract"; +import {ListIterator} from "../interface"; /** Expected API of this View's type. This interface allows to break a recursive dependency between types and views */ export type ArrayCompositeType< @@ -104,6 +105,22 @@ export class ArrayCompositeTreeView< return views; } + /** + * Similar to getAllReadonly but support ListIterator interface. + * Use ReusableListIterator to reuse over multiple calls. + */ + getAllReadonlyIter(views?: ListIterator>): ListIterator> { + const length = this.length; + const chunksNode = this.type.tree_getChunksNode(this.node); + const nodes = getNodesAtDepth(chunksNode, this.type.chunkDepth, 0, length); + views = views ?? new Array>(); + for (let i = 0; i < length; i++) { + // TODO: Optimize + views.push(this.type.elementType.getView(new Tree(nodes[i]))); + } + return views; + } + /** * Returns an array of values of all elements in the array, from index zero to `this.length - 1`. * The returned values are not Views so any changes won't be propagated upwards. @@ -122,4 +139,19 @@ export class ArrayCompositeTreeView< } return values; } + + /** + * Similar to getAllReadonlyValues but support ListIterator interface. + * Use ReusableListIterator to reuse over multiple calls. + */ + getAllReadonlyValuesIter(values?: ListIterator>): ListIterator> { + const length = this.length; + const chunksNode = this.type.tree_getChunksNode(this.node); + const nodes = getNodesAtDepth(chunksNode, this.type.chunkDepth, 0, length); + values = values ?? new Array>(); + for (let i = 0; i < length; i++) { + values.push(this.type.elementType.tree_toValue(nodes[i])); + } + return values; + } } diff --git a/packages/ssz/src/viewDU/arrayComposite.ts b/packages/ssz/src/viewDU/arrayComposite.ts index f420798c..6856c63a 100644 --- a/packages/ssz/src/viewDU/arrayComposite.ts +++ b/packages/ssz/src/viewDU/arrayComposite.ts @@ -162,12 +162,13 @@ export class ArrayCompositeTreeViewDU< } /** - * Similar to getAllReadonly but support returning a LiaIterator that could be reused + * Similar to getAllReadonly but support ListIterator interface. + * Use ReusableListIterator to reuse over multiple calls. */ getAllReadonlyIter(views?: ListIterator>): ListIterator> { this.populateAllNodes(); - views = views ?? new Array>(this._length); + views = views ?? new Array>(); for (let i = 0; i < this._length; i++) { const view = this.type.elementType.getViewDU(this.nodes[i], this.caches[i]); views.push(view); @@ -192,12 +193,13 @@ export class ArrayCompositeTreeViewDU< } /** - * WARNING: Returns all commited changes, if there are any pending changes commit them beforehand + * Similar to getAllReadonlyValues but support ListIterator interface. + * Use ReusableListIterator to reuse over multiple calls. */ getAllReadonlyValuesIter(values?: ListIterator>): ListIterator> { this.populateAllNodes(); - values = values ?? new Array>(this._length); + values = values ?? new Array>(); for (let i = 0; i < this._length; i++) { const value = this.type.elementType.tree_toValue(this.nodes[i]); values.push(value); diff --git a/packages/ssz/test/perf/byType/listComposite.test.ts b/packages/ssz/test/perf/byType/listComposite.test.ts index dbf75988..32762cfb 100644 --- a/packages/ssz/test/perf/byType/listComposite.test.ts +++ b/packages/ssz/test/perf/byType/listComposite.test.ts @@ -1,5 +1,13 @@ import {itBench} from "@dapplion/benchmark"; -import {ContainerNodeStructType, ContainerType, ListCompositeType, UintNumberType} from "../../../src"; +import { + CompositeViewDU, + ContainerNodeStructType, + ContainerType, + ListCompositeType, + ReusableListIterator, + UintNumberType, + ValueOf, +} from "../../../src"; const byteType = new UintNumberType(1); @@ -20,33 +28,53 @@ describe("ListCompositeType types", () => { }); } - for (const type of [ - new ListCompositeType(containerType, 2 ** 40, {typeName: "List(Container)"}), - new ListCompositeType(containerNodeStructType, 2 ** 40, {typeName: "List(ContainerNodeStruct)"}), - ]) { - const viewDU = type.toViewDU(newFilledArray(len, {a: 1, b: 2})); + for (const [i, type] of [containerType, containerNodeStructType].entries()) { + const listType = new ListCompositeType(type, 2 ** 40, { + typeName: `List(${i === 0 ? "Container" : "ContainerNodeStruct"})`, + }); + const viewDU = listType.toViewDU(newFilledArray(len, {a: 1, b: 2})); - itBench(`${type.typeName} len ${len} ViewDU.getAllReadonly() + iterate`, () => { + itBench(`${listType.typeName} len ${len} ViewDU.getAllReadonly() + iterate`, () => { const values = viewDU.getAllReadonly(); for (let i = 0; i < len; i++) { values[i]; } }); - itBench(`${type.typeName} len ${len} ViewDU.getAllReadonlyValues() + iterate`, () => { + const viewDUs = new ReusableListIterator>(); + itBench(`${listType.typeName} len ${len} ViewDU.getAllReadonlyIter() + iterate`, () => { + viewDUs.reset(); + viewDU.getAllReadonlyIter(viewDUs); + viewDUs.clean(); + for (const viewDU of viewDUs) { + viewDU; + } + }); + + itBench(`${listType.typeName} len ${len} ViewDU.getAllReadonlyValues() + iterate`, () => { const values = viewDU.getAllReadonlyValues(); for (let i = 0; i < len; i++) { values[i]; } }); - itBench(`${type.typeName} len ${len} ViewDU.get(i)`, () => { + const values = new ReusableListIterator>(); + itBench(`${listType.typeName} len ${len} ViewDU.getAllReadonlyValuesIter() + iterate`, () => { + values.clean(); + viewDU.getAllReadonlyValuesIter(values); + values.reset(); + for (const value of values) { + value; + } + }); + + itBench(`${listType.typeName} len ${len} ViewDU.get(i)`, () => { for (let i = 0; i < len; i++) { viewDU.get(i); } }); - itBench(`${type.typeName} len ${len} ViewDU.getReadonly(i)`, () => { + itBench(`${listType.typeName} len ${len} ViewDU.getReadonly(i)`, () => { for (let i = 0; i < len; i++) { viewDU.getReadonly(i); } diff --git a/packages/ssz/test/perf/iterate.test.ts b/packages/ssz/test/perf/iterate.test.ts index 92b923e3..f585dffd 100644 --- a/packages/ssz/test/perf/iterate.test.ts +++ b/packages/ssz/test/perf/iterate.test.ts @@ -1,6 +1,6 @@ import {itBench, setBenchOpts} from "@dapplion/benchmark"; -import {ListBasicType, UintNumberType} from "../../src"; -import {Validators} from "../lodestarTypes/phase0/sszTypes"; +import {CompositeViewDU, ListBasicType, ReusableListIterator, UintNumberType} from "../../src"; +import {Validators, Validator} from "../lodestarTypes/phase0/sszTypes"; describe("iterate", () => { setBenchOpts({noThreshold: true}); @@ -53,6 +53,22 @@ describe("readonly values - iterator vs array", () => { validatorsArray[i]; } }); + + const viewDUs = new ReusableListIterator>(); + itBench("compositeListValue.getAllReadonlyIter()", () => { + viewDUs.reset(); + validators.getAllReadonlyIter(viewDUs); + viewDUs.clean(); + }); + + itBench("compositeListValue.getAllReadonlyIter() + loop all", () => { + viewDUs.reset(); + validators.getAllReadonlyIter(viewDUs); + viewDUs.clean(); + for (const viewDU of viewDUs) { + viewDU; + } + }); }); // eslint-disable-next-line @typescript-eslint/explicit-function-return-type From ad0c4cde0b74ece1bbae7536af92b04a05b9b43e Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Wed, 31 Jul 2024 15:18:06 +0700 Subject: [PATCH 6/6] chore: update unit tests --- .../unit/byType/listComposite/tree.test.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/ssz/test/unit/byType/listComposite/tree.test.ts b/packages/ssz/test/unit/byType/listComposite/tree.test.ts index 2f146313..5b738c88 100644 --- a/packages/ssz/test/unit/byType/listComposite/tree.test.ts +++ b/packages/ssz/test/unit/byType/listComposite/tree.test.ts @@ -1,5 +1,13 @@ import {expect} from "chai"; -import {CompositeView, ContainerType, ListCompositeType, toHexString, UintNumberType, ValueOf} from "../../../../src"; +import { + CompositeView, + ContainerType, + ListCompositeType, + ReusableListIterator, + toHexString, + UintNumberType, + ValueOf, +} from "../../../../src"; import {ArrayCompositeTreeViewDU} from "../../../../src/viewDU/arrayComposite"; import {ssz} from "../../../lodestarTypes/primitive"; import {runViewTestMutation} from "../runViewTestMutation"; @@ -112,11 +120,21 @@ describe("ListCompositeType tree reads", () => { // Only for viewDU if (view instanceof ArrayCompositeTreeViewDU) { expect(() => view.getAllReadonly()).to.throw("Must commit changes before reading all nodes"); + expect(() => view.getAllReadonlyIter()).to.throw("Must commit changes before reading all nodes"); view.commit(); } expect(view.getAllReadonly().map(elementToValue)).deep.equals(values, "Wrong getAllReadonly()"); + (view.getAllReadonlyIter() as CompositeView[]).map(elementToValue); expect(view.getAllReadonlyValues()).deep.equals(values, "Wrong getAllReadonlyValues()"); + const result = new ReusableListIterator>(); + view.getAllReadonlyValuesIter(result); + expect(result.toArray()).deep.equals(values, "Wrong getAllReadonlyValues()"); + // reuse ReusableListIterator + result.reset(); + view.getAllReadonlyValuesIter(result); + result.clean(); + expect(result.toArray()).deep.equals(values, "Wrong getAllReadonlyValues()"); // Only for viewDU if (view instanceof ArrayCompositeTreeViewDU) {