@@ -123,6 +123,17 @@ class BlockAllocator {
123123 */
124124 _tailFree = null ;
125125
126+ /**
127+ * Advisory starting point for the next free-block search. Avoids repeatedly scanning past
128+ * small fragments at the start of the free list during sequential allocations. Cleared
129+ * (set to null) on free() and defrag(), causing the next search to restart from _headFree
130+ * since those operations may create earlier gaps.
131+ *
132+ * @type {MemBlock|null }
133+ * @private
134+ */
135+ _freeHint = null ;
136+
126137 /**
127138 * Pool of recycled MemBlock objects.
128139 *
@@ -164,22 +175,23 @@ class BlockAllocator {
164175 _freeRegionCount = 0 ;
165176
166177 /**
167- * Minimum growth increment used by {@link BlockAllocator#updateAllocation}.
178+ * Multiplicative growth factor used by {@link BlockAllocator#updateAllocation}.
179+ * When growing, the new capacity is at least `capacity * growMultiplier`.
168180 *
169181 * @type {number }
170182 * @private
171183 */
172- _growSize ;
184+ _growMultiplier ;
173185
174186 /**
175187 * Create a new BlockAllocator.
176188 *
177189 * @param {number } [capacity] - Initial address space capacity. Defaults to 0.
178- * @param {number } [growSize ] - Minimum growth increment for auto-grow in
179- * {@link BlockAllocator#updateAllocation}. Defaults to 1024 .
190+ * @param {number } [growMultiplier ] - Multiplicative growth factor for auto-grow in
191+ * {@link BlockAllocator#updateAllocation}. Defaults to 1.1 (10% extra) .
180192 */
181- constructor ( capacity = 0 , growSize = 1024 ) {
182- this . _growSize = growSize ;
193+ constructor ( capacity = 0 , growMultiplier = 1.1 ) {
194+ this . _growMultiplier = growMultiplier ;
183195 if ( capacity > 0 ) {
184196 this . _capacity = capacity ;
185197 this . _freeSize = capacity ;
@@ -188,6 +200,7 @@ class BlockAllocator {
188200 this . _tailAll = block ;
189201 this . _headFree = block ;
190202 this . _tailFree = block ;
203+ this . _freeHint = block ;
191204 this . _freeRegionCount = 1 ;
192205 }
193206 }
@@ -339,6 +352,9 @@ class BlockAllocator {
339352 * @private
340353 */
341354 _removeFromFreeList ( block ) {
355+ if ( this . _freeHint === block ) {
356+ this . _freeHint = block . _nextFree ;
357+ }
342358 if ( block . _prevFree ) block . _prevFree . _nextFree = block . _nextFree ;
343359 else this . _headFree = block . _nextFree ;
344360 if ( block . _nextFree ) block . _nextFree . _prevFree = block . _prevFree ;
@@ -349,18 +365,36 @@ class BlockAllocator {
349365 }
350366
351367 /**
352- * Scan the free list for the first block with size >= requested.
368+ * Scan the free list for the first block with size >= requested, starting from _freeHint
369+ * to skip past small fragments already examined by recent allocations.
353370 *
354371 * @param {number } size - Minimum size needed.
355372 * @returns {MemBlock|null } The first fitting free block, or null.
356373 * @private
357374 */
358375 _findFreeBlock ( size ) {
359- let block = this . _headFree ;
376+ const hint = this . _freeHint ;
377+ let block = hint ?? this . _headFree ;
360378 while ( block ) {
361- if ( block . _size >= size ) return block ;
379+ if ( block . _size >= size ) {
380+ this . _freeHint = block ;
381+ return block ;
382+ }
362383 block = block . _nextFree ;
363384 }
385+
386+ // Hint skipped earlier blocks — retry from head up to the hint
387+ if ( hint ) {
388+ block = this . _headFree ;
389+ while ( block !== hint ) {
390+ if ( block . _size >= size ) {
391+ this . _freeHint = block ;
392+ return block ;
393+ }
394+ block = block . _nextFree ;
395+ }
396+ }
397+
364398 return null ;
365399 }
366400
@@ -406,6 +440,7 @@ class BlockAllocator {
406440 block . _free = true ;
407441 this . _usedSize -= block . _size ;
408442 this . _freeSize += block . _size ;
443+ this . _freeHint = null ;
409444
410445 const prev = block . _prev ;
411446 const next = block . _next ;
@@ -492,6 +527,7 @@ class BlockAllocator {
492527 */
493528 defrag ( maxMoves = 0 , result = new Set ( ) ) {
494529 result . clear ( ) ;
530+ this . _freeHint = null ;
495531
496532 if ( this . _freeRegionCount === 0 ) return result ;
497533
@@ -724,10 +760,11 @@ class BlockAllocator {
724760 totalRemaining += /** @type {number } */ ( toAllocate [ j ] ) ;
725761 }
726762
727- // Grow if needed
763+ // Grow if needed, or if free space would be below headroom threshold
728764 const neededCapacity = this . _usedSize + totalRemaining ;
729- if ( neededCapacity > this . _capacity ) {
730- this . grow ( Math . max ( this . _capacity + this . _growSize , neededCapacity ) ) ;
765+ const headroomCapacity = Math . ceil ( neededCapacity * this . _growMultiplier ) ;
766+ if ( headroomCapacity > this . _capacity ) {
767+ this . grow ( headroomCapacity ) ;
731768 }
732769
733770 // Full defrag: compact everything
0 commit comments