33 * PROJECT: MemoryArenaPrototype.Lane
44 * FILE: SlowLane.cs
55 * PURPOSE: Memory store for long lived data and stuff we could not hold into the fast lane.
6- * Ids for Allocations is always negative here.
6+ * Ids for Allocations is always negative here.
77 * PROGRAMMER: Peter Geinitz (Wayfarer)
88 */
99
10- // TODO Compaction
11- // we should configure two options good enough or full compaction
12- // via the enum CompactionStyle.
13- // Good enough would stop as soon as a big enough gap is opened, while full would always compact everything to the start of the buffer.
14-
1510// ReSharper disable EventNeverSubscribedTo.Global
1611
1712using ExtendedSystemObjects ;
@@ -83,6 +78,11 @@ public sealed class SlowLane : IMemoryLane, IDisposable
8378 /// </summary>
8479 private readonly int _blobThreshold = 256 ;
8580
81+ /// <summary>
82+ /// The physical byte capacity assigned to the BlobManager sub-allocator.
83+ /// </summary>
84+ private readonly int _blobCapacity ;
85+
8686 /// <summary>
8787 /// The specialized manager for tiny/unpredictable allocations.
8888 /// </summary>
@@ -103,6 +103,11 @@ public sealed class SlowLane : IMemoryLane, IDisposable
103103 /// </summary>
104104 private readonly AllocationStrategy _searchStrategy ;
105105
106+ /// <summary>
107+ /// Gets or sets the compaction strategy used when defragmenting the lane buffer.
108+ /// </summary>
109+ public CompactionStyle CompactionStyle { get ; set ; }
110+
106111 /// <summary>
107112 /// Initializes a new instance of the <see cref="SlowLane" /> class.
108113 /// </summary>
@@ -111,14 +116,21 @@ public sealed class SlowLane : IMemoryLane, IDisposable
111116 /// <param name="blobThreshold">The BLOB threshold.</param>
112117 /// <param name="maxEntries">The maximum entries.</param>
113118 /// <param name="slowLaneFreeListStrategy">The slow lane free list strategy.</param>
114- public unsafe SlowLane ( int capacity , double blobCapacityFraction = 0.20 , int blobThreshold = 256 ,
115- int maxEntries = 1024 , AllocationStrategy slowLaneFreeListStrategy = default )
119+ /// <param name="compactionStyle">The compaction style policy (Full or GoodEnough).</param>
120+ public unsafe SlowLane (
121+ int capacity ,
122+ double blobCapacityFraction = 0.20 ,
123+ int blobThreshold = 256 ,
124+ int maxEntries = 1024 ,
125+ AllocationStrategy slowLaneFreeListStrategy = default ,
126+ CompactionStyle compactionStyle = CompactionStyle . Full )
116127 {
117128 Capacity = capacity ;
118129 _blobThreshold = blobThreshold ;
119130
120- // CAPTURE STRATEGY : Persist the configuration choice for all future allocation passes
131+ // CAPTURE STRATEGIES : Persist configured choices
121132 _searchStrategy = slowLaneFreeListStrategy ;
133+ CompactionStyle = compactionStyle ;
122134
123135 Buffer = Marshal . AllocHGlobal ( capacity ) ;
124136 _entries = new AllocationEntry [ maxEntries ] ;
@@ -128,12 +140,12 @@ public unsafe SlowLane(int capacity, double blobCapacityFraction = 0.20, int blo
128140 _versions = ( uint * ) NativeMemory . AllocZeroed ( ( nuint ) _versionsCapacity , sizeof ( uint ) ) ;
129141
130142 // Use the fraction provided by the config/constructor
131- var blobCapacity = ( int ) ( capacity * blobCapacityFraction ) ;
143+ _blobCapacity = ( int ) ( capacity * blobCapacityFraction ) ;
132144
133- _freeBlocks [ 0 ] = new FreeBlock { Offset = blobCapacity , Size = Capacity - blobCapacity } ;
145+ _freeBlocks [ 0 ] = new FreeBlock { Offset = _blobCapacity , Size = Capacity - _blobCapacity } ;
134146 _freeBlockCount = 1 ;
135147
136- _blobManager = new BlobManager ( Buffer , blobCapacity ) ;
148+ _blobManager = new BlobManager ( Buffer , _blobCapacity ) ;
137149 }
138150
139151 /// <summary>
@@ -206,11 +218,17 @@ public unsafe MemoryHandle Allocate(
206218
207219 // Calculate tracking footprint dimension rules including canary bytes
208220 var physicalSizeNeeded = MemoryCanary . GetPhysicalSize ( size ) ;
209- var offset = MemoryLaneUtils . FindFreeSpot ( physicalSizeNeeded , ref _freeBlocks , ref _freeBlockCount ,
210- _searchStrategy ) ;
221+ var offset = MemoryLaneUtils . FindFreeSpot ( physicalSizeNeeded , ref _freeBlocks , ref _freeBlockCount , _searchStrategy ) ;
211222
223+ // AUTO-HEALING: If fragmented, trigger policy-based compaction to open up a contiguous gap
212224 if ( offset == - 1 )
213- throw new OutOfMemoryException ( "SlowLane: Cannot allocate - No contiguous block large enough." ) ;
225+ {
226+ Compact ( CompactionStyle , size ) ;
227+ offset = MemoryLaneUtils . FindFreeSpot ( physicalSizeNeeded , ref _freeBlocks , ref _freeBlockCount , _searchStrategy ) ;
228+
229+ if ( offset == - 1 )
230+ throw new OutOfMemoryException ( "SlowLane: Cannot allocate - No contiguous block large enough after compaction." ) ;
231+ }
214232
215233 // Shift user coordinates forward safely past the pre-canary buffer space
216234 var userOffset = MemoryCanary . GetUserOffset ( offset ) ;
@@ -405,14 +423,21 @@ public unsafe void FreeMany(MemoryHandle[] handles)
405423 }
406424
407425 /// <inheritdoc />
408- public unsafe void Compact ( )
426+ public void Compact ( )
409427 {
410- if ( _entries == null || _handleIndex . Count == 0 ) return ;
428+ Compact ( CompactionStyle , 0 ) ;
429+ }
411430
412- var newBuffer = Marshal . AllocHGlobal ( Capacity ) ;
413- var offset = 0 ;
431+ /// <summary>
432+ /// Compacts the unmanaged lane buffer using the specified policy and target size requirement.
433+ /// </summary>
434+ /// <param name="style">The compaction style (Full or GoodEnough).</param>
435+ /// <param name="requiredSize">Target size required if GoodEnough style is active.</param>
436+ public unsafe void Compact ( CompactionStyle style , int requiredSize = 0 )
437+ {
438+ if ( _entries == null || _handleIndex . Count == 0 ) return ;
414439
415- // 1. Extract only the living, valid entries using our dictionary
440+ // 1. Extract non-stub living entries
416441 var validEntries = new List < AllocationEntry > ( _handleIndex . Count ) ;
417442 foreach ( var index in _handleIndex . Values )
418443 {
@@ -423,67 +448,124 @@ public unsafe void Compact()
423448 }
424449 }
425450
426- // 2. Sort them by their physical Offset in the buffer
427- validEntries . Sort ( ( a , b ) => a . Offset . CompareTo ( b . Offset ) ) ;
451+ if ( validEntries . Count == 0 )
452+ {
453+ _freeBlocks [ 0 ] = new FreeBlock
454+ {
455+ Offset = _blobCapacity ,
456+ Size = Capacity - _blobCapacity
457+ } ;
458+ _freeBlockCount = 1 ;
459+ _freeSlots . Clear ( ) ;
460+ EntryCount = 0 ;
461+ return ;
462+ }
428463
429- var writeIndex = 0 ;
430- var newHandleIndex = new Dictionary < int , int > ( validEntries . Count ) ;
464+ // 2. Sort by current physical offset ascending
465+ validEntries . Sort ( ( a , b ) => MemoryCanary . GetPhysicalOffset ( a . Offset ) . CompareTo ( MemoryCanary . GetPhysicalOffset ( b . Offset ) ) ) ;
431466
432- // 3. Copy them sequentially to the new buffer
433- foreach ( var entry in validEntries )
467+ var currentOffset = _blobCapacity ;
468+ var physicalTargetNeeded = requiredSize > 0 ? MemoryCanary . GetPhysicalSize ( requiredSize ) : 0 ;
469+ var isGoodEnough = style == CompactionStyle . GoodEnough ;
470+
471+ // 3. Perform surgical in-place sliding
472+ for ( var i = 0 ; i < validEntries . Count ; i ++ )
434473 {
435- var currentEntry = entry ; // Make a local copy to modify
474+ var entry = validEntries [ i ] ;
475+ var srcPhysicalOffset = MemoryCanary . GetPhysicalOffset ( entry . Offset ) ;
476+ var physicalSize = MemoryCanary . GetPhysicalSize ( entry . Size ) ;
436477
437- // Extract base track coordinates for the total block footprint
438- var srcPhysicalOffset = MemoryCanary . GetPhysicalOffset ( currentEntry . Offset ) ;
439- var physicalSize = MemoryCanary . GetPhysicalSize ( currentEntry . Size ) ;
478+ if ( srcPhysicalOffset != currentOffset )
479+ {
480+ // Slide block in-place (System.Buffer.MemoryCopy handles overlapping regions safely when copying downward)
481+ System . Buffer . MemoryCopy (
482+ ( void * ) ( Buffer + srcPhysicalOffset ) ,
483+ ( void * ) ( Buffer + currentOffset ) ,
484+ physicalSize ,
485+ physicalSize ) ;
440486
441- // Slide the complete unmanaged layout block (Canary + User Data + Canary)
442- System . Buffer . MemoryCopy (
443- ( void * ) ( Buffer + srcPhysicalOffset ) ,
444- ( void * ) ( newBuffer + offset ) ,
445- physicalSize ,
446- physicalSize ) ;
487+ entry . Offset = MemoryCanary . GetUserOffset ( currentOffset ) ;
447488
448- // Realign tracked metadata pointers to target the new user offset location
449- currentEntry . Offset = MemoryCanary . GetUserOffset ( offset ) ;
450- offset += physicalSize ;
489+ if ( _handleIndex . TryGetValue ( entry . HandleId , out var slotIndex ) )
490+ {
491+ _entries [ slotIndex ] = entry ;
492+ }
451493
452- EnsureEntryCapacity ( writeIndex ) ;
453- _entries [ writeIndex ] = currentEntry ;
454- newHandleIndex [ currentEntry . HandleId ] = writeIndex ;
494+ validEntries [ i ] = entry ;
495+ }
455496
456- writeIndex ++ ;
457- }
497+ currentOffset += physicalSize ;
458498
459- // 4. Clear all remaining slots
460- for ( var i = writeIndex ; i < _entries . Length ; i ++ )
461- {
462- _entries [ i ] = default ;
499+ // 4. Evaluate "GoodEnough" stopping condition
500+ if ( isGoodEnough && physicalTargetNeeded > 0 )
501+ {
502+ var nextEntryOffset = i + 1 < validEntries . Count
503+ ? MemoryCanary . GetPhysicalOffset ( validEntries [ i + 1 ] . Offset )
504+ : Capacity ;
505+
506+ var gap = nextEntryOffset - currentOffset ;
507+ if ( gap >= physicalTargetNeeded )
508+ {
509+ // Target gap created! Stop shifting remaining blocks to save CPU cycles.
510+ break ;
511+ }
512+ }
463513 }
464514
465- // 5. Update the internal state
466- Marshal . FreeHGlobal ( Buffer ) ;
467- Buffer = newBuffer ;
515+ // 5. Rebuild free-list blocks accurately
516+ RebuildFreeBlocks ( validEntries ) ;
468517
469- _handleIndex . Clear ( ) ;
470- foreach ( var kv in newHandleIndex )
518+ OnCompaction ? . Invoke ( nameof ( SlowLane ) ) ;
519+ }
520+
521+ /// <summary>
522+ /// Rebuilds the free block list based on active allocation positions.
523+ /// </summary>
524+ private void RebuildFreeBlocks ( List < AllocationEntry > validEntries )
525+ {
526+ validEntries . Sort ( ( a , b ) => MemoryCanary . GetPhysicalOffset ( a . Offset ) . CompareTo ( MemoryCanary . GetPhysicalOffset ( b . Offset ) ) ) ;
527+
528+ _freeBlockCount = 0 ;
529+ var cursor = _blobCapacity ;
530+
531+ foreach ( var entry in validEntries )
471532 {
472- _handleIndex [ kv . Key ] = kv . Value ;
473- }
533+ var physOffset = MemoryCanary . GetPhysicalOffset ( entry . Offset ) ;
534+ var physSize = MemoryCanary . GetPhysicalSize ( entry . Size ) ;
535+
536+ if ( physOffset > cursor )
537+ {
538+ EnsureFreeBlockArrayCapacity ( _freeBlockCount + 1 ) ;
539+ _freeBlocks [ _freeBlockCount ++ ] = new FreeBlock
540+ {
541+ Offset = cursor ,
542+ Size = physOffset - cursor
543+ } ;
544+ }
474545
475- EntryCount = writeIndex ;
476- _freeSlots . Clear ( ) ; // No more holes in the array!
546+ cursor = physOffset + physSize ;
547+ }
477548
478- // 6. Reset the Free-List!
479- _freeBlocks [ 0 ] = new FreeBlock
549+ if ( cursor < Capacity )
480550 {
481- Offset = offset ,
482- Size = Capacity - offset
483- } ;
484- _freeBlockCount = 1 ;
551+ EnsureFreeBlockArrayCapacity ( _freeBlockCount + 1 ) ;
552+ _freeBlocks [ _freeBlockCount ++ ] = new FreeBlock
553+ {
554+ Offset = cursor ,
555+ Size = Capacity - cursor
556+ } ;
557+ }
558+ }
485559
486- OnCompaction ? . Invoke ( nameof ( SlowLane ) ) ;
560+ /// <summary>
561+ /// Ensures free blocks array has capacity for required count.
562+ /// </summary>
563+ private void EnsureFreeBlockArrayCapacity ( int requiredCount )
564+ {
565+ if ( _freeBlocks . Length < requiredCount )
566+ {
567+ Array . Resize ( ref _freeBlocks , Math . Max ( _freeBlocks . Length * 2 , requiredCount ) ) ;
568+ }
487569 }
488570
489571 /// <inheritdoc />
@@ -573,7 +655,7 @@ private int GetUsed()
573655 /// <inheritdoc />
574656 public int FreeSpace ( )
575657 {
576- // FIX: Passes the physical tracking source lists to clear type signature compilation errors
658+ // Passes the physical tracking source lists to clear type signature compilation errors
577659 var mainFreeSpace = MemoryLaneUtils . CalculateFreeSpace ( _freeBlocks , _freeBlockCount ) ;
578660 var blobFreeSpace = _blobManager ? . FreeSpace ( ) ?? 0 ;
579661
@@ -628,7 +710,7 @@ public int StubCount()
628710 /// <inheritdoc />
629711 public int EstimateFragmentation ( )
630712 {
631- // FIX: Targets the true unmanaged free blocks tracking array for precise fragmentation readout
713+ // Targets the true unmanaged free blocks tracking array for precise fragmentation readout
632714 return MemoryLaneUtils . EstimateFragmentation ( _freeBlocks , _freeBlockCount ) ;
633715 }
634716
0 commit comments