Skip to content

Commit 0c4bc07

Browse files
author
LoneWandererProductions
committed
Improve fastlane
1 parent 5c94244 commit 0c4bc07

1 file changed

Lines changed: 64 additions & 21 deletions

File tree

MemoryManager.Lanes/FastLane.cs

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* PROJECT: MemoryArenaPrototype.Lane
44
* FILE: FastLane.cs
55
* PURPOSE: Memory store for short lived data, smaller one preferable but that is up to the user.
6-
* Ids for Allocations is always positive here.
6+
* Ids for Allocations is always positive here.
77
* PROGRAMMER: Peter Geinitz (Wayfarer)
88
*/
99

@@ -64,9 +64,14 @@ public sealed class FastLane : IFastLane, IDisposable
6464
private FreeBlock[] _freeBlocks = new FreeBlock[128];
6565

6666
/// <summary>
67-
/// The versions
67+
/// Pointer to the flat versions array. Index matches handle ID.
6868
/// </summary>
69-
private readonly UnmanagedMap<uint> _versions;
69+
private unsafe uint* _versions;
70+
71+
/// <summary>
72+
/// Current capacity of the versions array.
73+
/// </summary>
74+
private int _versionsCapacity;
7075

7176
/// <summary>
7277
/// The free block count
@@ -79,7 +84,7 @@ public sealed class FastLane : IFastLane, IDisposable
7984
/// <param name="size">The size.</param>
8085
/// <param name="slowLane">The slow lane.</param>
8186
/// <param name="maxEntries">The maximum entries.</param>
82-
public FastLane(int size, SlowLane slowLane, int maxEntries = 1024)
87+
public unsafe FastLane(int size, SlowLane slowLane, int maxEntries = 1024)
8388
{
8489
_slowLane = slowLane;
8590
Capacity = size;
@@ -88,7 +93,9 @@ public FastLane(int size, SlowLane slowLane, int maxEntries = 1024)
8893
// PRE-ALLOCATE everything based on maxEntries
8994
_entries = new AllocationEntry[maxEntries];
9095
_freeBlocks = new FreeBlock[maxEntries]; // Free blocks can theoretically equal maxEntries
91-
_versions = new UnmanagedMap<uint>(maxEntries);
96+
// INITIALIZATION: Allocate flat tracking for versions
97+
_versionsCapacity = maxEntries + 1;
98+
_versions = (uint*)NativeMemory.AllocZeroed((nuint)_versionsCapacity, sizeof(uint));
9299

93100
// INITIALIZATION: The entire lane starts as one giant free block
94101
_freeBlocks[0] = new FreeBlock { Offset = 0, Size = Capacity };
@@ -106,7 +113,6 @@ public FastLane(int size, SlowLane slowLane, int maxEntries = 1024)
106113
/// <inheritdoc />
107114
public int EntryCount { get; private set; }
108115

109-
110116
/// <inheritdoc />
111117
public OneWayLane? OneWayLane { get; set; }
112118

@@ -122,10 +128,17 @@ public FastLane(int size, SlowLane slowLane, int maxEntries = 1024)
122128
/// <summary>
123129
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
124130
/// </summary>
125-
public void Dispose()
131+
public unsafe void Dispose()
126132
{
127133
Marshal.FreeHGlobal(Buffer);
128134
_handleIndex.Clear();
135+
136+
if (_versions != null)
137+
{
138+
NativeMemory.Free(_versions);
139+
_versions = null;
140+
}
141+
129142
_entries = null;
130143
GC.SuppressFinalize(this);
131144
}
@@ -155,7 +168,7 @@ public bool CanAllocate(int size)
155168
/// <returns>Handler to the reserved memory.</returns>
156169
/// <exception cref="T:System.InvalidOperationException">FastLane: Memory not reserved</exception>
157170
/// <exception cref="T:System.OutOfMemoryException">FastLane: Not enough memory</exception>
158-
public MemoryHandle Allocate(
171+
public unsafe MemoryHandle Allocate(
159172
int size,
160173
AllocationPriority priority = AllocationPriority.Normal,
161174
AllocationHints hints = AllocationHints.None,
@@ -167,19 +180,22 @@ public MemoryHandle Allocate(
167180
var offset = MemoryLaneUtils.FindFreeSpot(size, ref _freeBlocks, ref _freeBlockCount);
168181

169182
if (offset == -1)
170-
throw new OutOfMemoryException("SlowLane: Cannot allocate - No contiguous block large enough.");
183+
throw new OutOfMemoryException("FastLane: Cannot allocate - No contiguous block large enough.");
171184

172185
EnsureEntryCapacity(EntryCount);
173186

174187
//So we reuse freed handles here
188+
// So we reuse freed handles here
175189
var id = MemoryLaneUtils.GetNextId(_freeIds, ref _nextHandleId);
176190

177-
// Increment the version for this ID slot ---
178-
// This ensures that any old handles still pointing to this ID will be rejected.
179-
_versions.TryGetValue(id, out var version);
180-
version++;
191+
// Ensure our flat array is large enough for this ID
192+
if (id >= _versionsCapacity)
193+
{
194+
GrowVersions(id + 1);
195+
}
181196

182-
_versions[id] = version;
197+
// True O(1) Version Bump
198+
uint version = ++_versions[id];
183199

184200
#if DEBUG
185201
if (!string.IsNullOrEmpty(debugName))
@@ -261,7 +277,7 @@ public void Free(MemoryHandle handle)
261277
{
262278
// 1. Remove from index first
263279
if (!_handleIndex.TryRemove(handle.Id, out var index))
264-
throw new InvalidOperationException($"SlowLane: Invalid handle {handle.Id}");
280+
throw new InvalidOperationException($"FastLane: Invalid handle {handle.Id}");
265281

266282
#if DEBUG
267283
_debugNames.Remove(handle.Id);
@@ -273,9 +289,8 @@ public void Free(MemoryHandle handle)
273289

274290
if (entry.IsStub && entry.RedirectToId != 0)
275291
{
276-
var slowHandle = new MemoryHandle(entry.RedirectToId, entry.Version, _slowLane);
292+
var slowHandle = new MemoryHandle(entry.RedirectToId, entry.RedirectVersion, _slowLane);
277293
_slowLane.Free(slowHandle);
278-
// Redirects.Remove(handle.Id); // We are going to delete this dictionary entirely in a second!
279294
}
280295

281296
// 3. Swap-with-tail removal
@@ -296,7 +311,6 @@ public void Free(MemoryHandle handle)
296311
_freeIds.Push(handle.Id);
297312
}
298313

299-
300314
/// <summary>
301315
/// Compacts the memory lane by consolidating free space and possibly rearranging allocations.
302316
/// Useful to reduce fragmentation and improve allocation efficiency.
@@ -472,7 +486,13 @@ private void EnsureEntryCapacity(int requiredSlotIndex)
472486
{
473487
var oldSize = _entries.Length;
474488
var newSize = MemoryLaneUtils.EnsureEntryCapacity(ref _entries, requiredSlotIndex);
475-
// Allocation Entriesmust be extended
489+
490+
// Defensive Safeguard: Ensure free blocks grow alongside entries
491+
if (_freeBlocks.Length < newSize)
492+
{
493+
Array.Resize(ref _freeBlocks, newSize);
494+
}
495+
476496
OnAllocationExtension?.Invoke(nameof(FastLane), oldSize, newSize);
477497
}
478498

@@ -563,8 +583,8 @@ public string DebugRedirections()
563583
// Pass the debug names dictionary in Debug mode
564584
return MemoryLaneUtils.DebugRedirections(_entries, EntryCount, _debugNames);
565585
#else
566-
// Pass null in Release mode since the dictionary doesn't exist
567-
return MemoryLaneUtils.DebugRedirections(_entries, EntryCount, null);
586+
// Pass null in Release mode since the dictionary doesn't exist
587+
return MemoryLaneUtils.DebugRedirections(_entries, EntryCount, null);
568588
#endif
569589
}
570590

@@ -580,6 +600,29 @@ public void LogDump()
580600
Trace.WriteLine($"--- {GetType().Name} Dump End ---");
581601
}
582602

603+
/// <summary>
604+
/// Grows the versions.
605+
/// </summary>
606+
/// <param name="minCapacity">The minimum capacity.</param>
607+
[MethodImpl(MethodImplOptions.NoInlining)]
608+
private unsafe void GrowVersions(int minCapacity)
609+
{
610+
var newCapacity = _versionsCapacity * 2;
611+
if (newCapacity < minCapacity) newCapacity = minCapacity;
612+
613+
var newVersions = (uint*)NativeMemory.AllocZeroed((nuint)newCapacity, sizeof(uint));
614+
615+
if (_versions != null)
616+
{
617+
// Copy old versions to the new block
618+
Unsafe.CopyBlock(newVersions, _versions, (uint)(_versionsCapacity * sizeof(uint)));
619+
NativeMemory.Free(_versions);
620+
}
621+
622+
_versions = newVersions;
623+
_versionsCapacity = newCapacity;
624+
}
625+
583626
/// <summary>
584627
/// Evaluates an allocation against current policies to see if it should be evicted.
585628
/// </summary>

0 commit comments

Comments
 (0)