From 2eaf4a309f3f6ff4587155daddfd098b892c98d5 Mon Sep 17 00:00:00 2001 From: Alp Date: Wed, 25 Feb 2026 04:55:16 -0500 Subject: [PATCH] perf(shared): replace closure-based BTree iterators with class instances Class-based iterators (BTreeForwardIterator/BTreeReverseIterator) store traversal state in instance properties instead of closure variables, which V8 optimizes better for hot iteration paths. Part of IVM pipeline perf optimizations that reduced page freeze from ~7.7s to <1s in a production app. * Add BTreeForwardIterator class with next() and [Symbol.iterator]() * Add BTreeReverseIterator class with next() and [Symbol.iterator]() * Modify valuesFrom() to return BTreeForwardIterator * Modify valuesFromReversed() to return BTreeReverseIterator * Keep iterator() helper for empty-tree case --- packages/shared/src/btree-set.test.ts | 47 ++++++++ packages/shared/src/btree-set.ts | 152 +++++++++++++++++--------- 2 files changed, 148 insertions(+), 51 deletions(-) diff --git a/packages/shared/src/btree-set.test.ts b/packages/shared/src/btree-set.test.ts index f75ba7ead0..1f778ba9ac 100644 --- a/packages/shared/src/btree-set.test.ts +++ b/packages/shared/src/btree-set.test.ts @@ -182,6 +182,53 @@ test('add should allow replacing equal entry', () => { ]); }); +suite('class-based iterators', () => { + test('forward iterator produces same results as spread', () => { + const t = new BTreeSet((a, b) => a - b); + for (let i = 0; i < 100; i++) { + t.add(i); + } + const forward = [...t.valuesFrom()]; + expect(forward).toEqual(Array.from({length: 100}, (_, i) => i)); + }); + + test('reverse iterator produces same results as spread', () => { + const t = new BTreeSet((a, b) => a - b); + for (let i = 0; i < 100; i++) { + t.add(i); + } + const reversed = [...t.valuesReversed()]; + expect(reversed).toEqual( + Array.from({length: 100}, (_, i) => 99 - i), + ); + }); + + test('empty tree iterator returns done immediately', () => { + const t = new BTreeSet((a, b) => a - b); + const fwd = t.values(); + expect(fwd.next()).toEqual({done: true, value: undefined}); + const rev = t.valuesReversed(); + expect(rev.next()).toEqual({done: true, value: undefined}); + }); + + test('single element tree works', () => { + const t = new BTreeSet((a, b) => a - b); + t.add(42); + expect([...t.values()]).toEqual([42]); + expect([...t.valuesReversed()]).toEqual([42]); + }); + + test('iterator protocol: [Symbol.iterator] returns self', () => { + const t = new BTreeSet((a, b) => a - b); + t.add(1); + t.add(2); + const fwd = t.values(); + expect(fwd[Symbol.iterator]()).toBe(fwd); + const rev = t.valuesReversed(); + expect(rev[Symbol.iterator]()).toBe(rev); + }); +}); + suite('fast-check', () => { test('OrderedSet Property-based Tests (large)', () => { assert( diff --git a/packages/shared/src/btree-set.ts b/packages/shared/src/btree-set.ts index e06f837d75..709dd71cbb 100644 --- a/packages/shared/src/btree-set.ts +++ b/packages/shared/src/btree-set.ts @@ -132,6 +132,105 @@ export class BTreeSet { } } +class BTreeForwardIterator implements IterableIterator { + _nodeQueue: BNode[][]; + _nodeIndex: number[]; + _leaf: BNode; + _i: number; + + constructor( + nodeQueue: BNode[][], + nodeIndex: number[], + leaf: BNode, + startI: number, + ) { + this._nodeQueue = nodeQueue; + this._nodeIndex = nodeIndex; + this._leaf = leaf; + this._i = startI; + } + + next(): IteratorResult { + for (;;) { + if (++this._i < this._leaf.keys.length) { + return {done: false, value: this._leaf.keys[this._i]}; + } + + let level = -1; + for (;;) { + if (++level >= this._nodeQueue.length) { + return {done: true, value: undefined as unknown as K}; + } + if (++this._nodeIndex[level] < this._nodeQueue[level].length) { + break; + } + } + for (; level > 0; level--) { + this._nodeQueue[level - 1] = ( + this._nodeQueue[level][this._nodeIndex[level]] as BNodeInternal + ).children; + this._nodeIndex[level - 1] = 0; + } + this._leaf = this._nodeQueue[0][this._nodeIndex[0]]; + this._i = -1; + } + } + + [Symbol.iterator]() { + return this; + } +} + +class BTreeReverseIterator implements IterableIterator { + _nodeQueue: BNode[][]; + _nodeIndex: number[]; + _leaf: BNode; + _i: number; + + constructor( + nodeQueue: BNode[][], + nodeIndex: number[], + leaf: BNode, + startI: number, + ) { + this._nodeQueue = nodeQueue; + this._nodeIndex = nodeIndex; + this._leaf = leaf; + this._i = startI; + } + + next(): IteratorResult { + for (;;) { + if (--this._i >= 0) { + return {done: false, value: this._leaf.keys[this._i]}; + } + + let level; + // Advance to the next leaf node + for (level = -1; ; ) { + if (++level >= this._nodeQueue.length) { + return {done: true, value: undefined as unknown as K}; + } + if (--this._nodeIndex[level] >= 0) { + break; + } + } + for (; level > 0; level--) { + this._nodeQueue[level - 1] = ( + this._nodeQueue[level][this._nodeIndex[level]] as BNodeInternal + ).children; + this._nodeIndex[level - 1] = this._nodeQueue[level - 1].length - 1; + } + this._leaf = this._nodeQueue[0][this._nodeIndex[0]]; + this._i = this._leaf.keys.length; + } + } + + [Symbol.iterator]() { + return this; + } +} + function valuesFrom( root: BNode, comparator: Comparator, @@ -158,31 +257,7 @@ function valuesFrom( i++; } - return iterator(() => { - for (;;) { - if (++i < leaf.keys.length) { - return {done: false, value: leaf.keys[i]}; - } - - let level = -1; - for (;;) { - if (++level >= nodeQueue.length) { - return {done: true, value: undefined}; - } - if (++nodeIndex[level] < nodeQueue[level].length) { - break; - } - } - for (; level > 0; level--) { - nodeQueue[level - 1] = ( - nodeQueue[level][nodeIndex[level]] as BNodeInternal - ).children; - nodeIndex[level - 1] = 0; - } - leaf = nodeQueue[0][nodeIndex[0]]; - i = -1; - } - }); + return new BTreeForwardIterator(nodeQueue, nodeIndex, leaf, i); } function valuesFromReversed( @@ -210,32 +285,7 @@ function valuesFromReversed( i++; } - return iterator(() => { - for (;;) { - if (--i >= 0) { - return {done: false, value: leaf.keys[i]}; - } - - let level; - // Advance to the next leaf node - for (level = -1; ; ) { - if (++level >= nodeQueue.length) { - return {done: true, value: undefined}; - } - if (--nodeIndex[level] >= 0) { - break; - } - } - for (; level > 0; level--) { - nodeQueue[level - 1] = ( - nodeQueue[level][nodeIndex[level]] as BNodeInternal - ).children; - nodeIndex[level - 1] = nodeQueue[level - 1].length - 1; - } - leaf = nodeQueue[0][nodeIndex[0]]; - i = leaf.keys.length; - } - }); + return new BTreeReverseIterator(nodeQueue, nodeIndex, leaf, i); } function findPath(