Skip to content

Commit 0420b8a

Browse files
author
LoneWandererProductions
committed
improve Slowlane
1 parent 0c4bc07 commit 0420b8a

1 file changed

Lines changed: 107 additions & 50 deletions

File tree

MemoryManager.Lanes/SlowLane.cs

Lines changed: 107 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
* COPYRIGHT: See COPYING in the top level directory
33
* PROJECT: MemoryArenaPrototype.Lane
44
* FILE: SlowLane.cs
5-
* PURPOSE: Memory store for long lived data and stuff we could not hold into he slow lane.
5+
* PURPOSE: Memory store for long lived data and stuff we could not hold into the fast lane.
66
* Ids for Allocations is always negative here.
77
* PROGRAMMER: Peter Geinitz (Wayfarer)
88
*/
99

1010
// ReSharper disable EventNeverSubscribedTo.Global
11+
#nullable enable
1112

1213
using ExtendedSystemObjects;
1314
using MemoryManager.Core;
@@ -19,7 +20,7 @@ namespace MemoryManager.Lanes
1920
{
2021
/// <inheritdoc cref="IMemoryLane" />
2122
/// <summary>
22-
/// The SlowLane for all the sorted out stuff and bigger longer resting data.
23+
/// The SlowLane for all the sorted out stuff and bigger longer resting data.
2324
/// </summary>
2425
/// <seealso cref="T:Core.IMemoryLane" />
2526
/// <seealso cref="T:System.IDisposable" />
@@ -33,32 +34,32 @@ public sealed class SlowLane : IMemoryLane, IDisposable
3334
#endif
3435

3536
/// <summary>
36-
/// The safety margin
37+
/// The safety margin
3738
/// </summary>
3839
private const double SafetyMargin = 0.10; // 10% free space reserved
3940

4041
/// <summary>
41-
/// The free ids
42+
/// The free ids
4243
/// </summary>
4344
private readonly UnmanagedIntList _freeIds = new(128);
4445

4546
/// <summary>
46-
/// The free slots, we reuse freed slots
47+
/// The free slots, we reuse freed slots
4748
/// </summary>
4849
private readonly UnmanagedIntList _freeSlots = new(128);
4950

5051
/// <summary>
51-
/// The handle index
52+
/// The handle index
5253
/// </summary>
5354
private readonly UnmanagedMap<int> _handleIndex = new(7); // handleId -> entries array index
5455

5556
/// <summary>
56-
/// The allocated entries
57+
/// The allocated entries
5758
/// </summary>
5859
private AllocationEntry[] _entries;
5960

6061
/// <summary>
61-
/// The next handle identifier
62+
/// The next handle identifier
6263
/// </summary>
6364
private int _nextHandleId = -1;
6465

@@ -73,20 +74,25 @@ public sealed class SlowLane : IMemoryLane, IDisposable
7374
private int _freeBlockCount;
7475

7576
/// <summary>
76-
/// The threshold. Anything smaller than this goes to the BlobManager.
77-
/// e.g., 256 bytes
77+
/// The threshold. Anything smaller than this goes to the BlobManager.
78+
/// e.g., 256 bytes
7879
/// </summary>
7980
private readonly int _blobThreshold = 256;
8081

8182
/// <summary>
82-
/// The specialized manager for tiny/unpredictable allocations.
83+
/// The specialized manager for tiny/unpredictable allocations.
8384
/// </summary>
8485
private readonly BlobManager? _blobManager;
8586

8687
/// <summary>
87-
/// The versions
88+
/// Pointer to the flat versions array. Index matches absolute handle ID (-id).
8889
/// </summary>
89-
private readonly UnmanagedMap<uint> _versions;
90+
private unsafe uint* _versions;
91+
92+
/// <summary>
93+
/// Current capacity of the versions array.
94+
/// </summary>
95+
private int _versionsCapacity;
9096

9197
/// <summary>
9298
/// Initializes a new instance of the <see cref="SlowLane" /> class.
@@ -95,15 +101,18 @@ public sealed class SlowLane : IMemoryLane, IDisposable
95101
/// <param name="blobCapacityFraction">The BLOB capacity fraction.</param>
96102
/// <param name="blobThreshold">The BLOB threshold.</param>
97103
/// <param name="maxEntries">The maximum entries.</param>
98-
public SlowLane(int capacity, double blobCapacityFraction = 0.20, int blobThreshold = 256,
104+
public unsafe SlowLane(int capacity, double blobCapacityFraction = 0.20, int blobThreshold = 256,
99105
int maxEntries = 1024)
100106
{
101107
Capacity = capacity;
102108
_blobThreshold = blobThreshold;
103109

104110
Buffer = Marshal.AllocHGlobal(capacity);
105111
_entries = new AllocationEntry[maxEntries];
106-
_versions = new UnmanagedMap<uint>(maxEntries);
112+
113+
// INITIALIZATION: Allocate flat tracking for versions (+1 to accommodate 1-based indexing of absolute IDs)
114+
_versionsCapacity = maxEntries + 1;
115+
_versions = (uint*)NativeMemory.AllocZeroed((nuint)_versionsCapacity, sizeof(uint));
107116

108117
// Use the fraction provided by the config/constructor
109118
var blobCapacity = (int)(capacity * blobCapacityFraction);
@@ -115,43 +124,49 @@ public SlowLane(int capacity, double blobCapacityFraction = 0.20, int blobThresh
115124
}
116125

117126
/// <summary>
118-
/// Gets or sets the buffer.
127+
/// Gets or sets the buffer.
119128
/// </summary>
120129
/// <value>
121-
/// The buffer.
130+
/// The buffer.
122131
/// </value>
123132
public nint Buffer { get; private set; }
124133

125134
/// <summary>
126-
/// Gets the capacity.
135+
/// Gets the capacity.
127136
/// </summary>
128137
/// <value>
129-
/// The capacity.
138+
/// The capacity.
130139
/// </value>
131140
public int Capacity { get; }
132141

133-
134142
/// <inheritdoc />
135143
public int EntryCount { get; private set; }
136144

137145
/// <inheritdoc />
138146
/// <summary>
139-
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
147+
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
140148
/// </summary>
141-
public void Dispose()
149+
public unsafe void Dispose()
142150
{
143151
Marshal.FreeHGlobal(Buffer);
144152
EntryCount = 0;
145153
_handleIndex.Clear();
146154
_freeSlots.Clear();
147155
_freeIds.Clear();
148156

157+
// clean up unmanaged versions array to prevent memory leaks
158+
if (_versions != null)
159+
{
160+
NativeMemory.Free(_versions);
161+
_versions = null;
162+
}
163+
149164
_blobManager?.Compact();
150165
}
151166

152167
/// <inheritdoc />
153168
/// <summary>
154-
/// Allocates the specified size.
169+
/// Allocates the specified size.
155170
/// </summary>
156171
/// <param name="size">The size.</param>
157172
/// <param name="priority">The priority.</param>
@@ -160,7 +175,7 @@ public void Dispose()
160175
/// <param name="currentFrame">The current frame.</param>
161176
/// <returns>Allocated memory and a reference.</returns>
162177
/// <exception cref="T:System.OutOfMemoryException">SlowLane: Cannot allocate</exception>
163-
public MemoryHandle Allocate(
178+
public unsafe MemoryHandle Allocate(
164179
int size,
165180
AllocationPriority priority = AllocationPriority.Normal,
166181
AllocationHints hints = AllocationHints.None,
@@ -184,7 +199,7 @@ public MemoryHandle Allocate(
184199
var slotIndex = _freeSlots.Length > 0 ? _freeSlots.Pop() : EntryCount++;
185200
EnsureEntryCapacity(slotIndex);
186201

187-
//So we reuse freed handles here
202+
// So we reuse freed handles here (Yields sequential negative IDs, e.g., -1, -2, -3...)
188203
var id = MemoryLaneUtils.GetNextId(_freeIds, ref _nextHandleId);
189204

190205
#if DEBUG
@@ -193,11 +208,15 @@ public MemoryHandle Allocate(
193208
_debugNames[id] = debugName;
194209
}
195210
#endif
196-
var currentVersion = _versions.TryGetValue(id, out var version)
197-
? (ushort)(version + 1)
198-
: (ushort)1;
211+
// Map sequential negative ID directly to positive array coordinates
212+
int versionIdx = -id;
213+
if (versionIdx >= _versionsCapacity)
214+
{
215+
GrowVersions(versionIdx + 1);
216+
}
199217

200-
_versions[id] = currentVersion;
218+
// True O(1) Version Bump
219+
uint currentVersion = ++_versions[versionIdx];
201220

202221
_entries[slotIndex] = new AllocationEntry
203222
{
@@ -242,14 +261,14 @@ public bool CanAllocate(int size)
242261

243262
/// <inheritdoc />
244263
/// <summary>
245-
/// Resolves the specified handle.
264+
/// Resolves the specified handle.
246265
/// </summary>
247266
/// <param name="handle">The handle.</param>
248267
/// <returns>Pointer to the stored data.</returns>
249268
/// <exception cref="T:System.InvalidOperationException">
250-
/// SlowLane: Invalid handle
251-
/// or
252-
/// SlowLane: Cannot resolve stub entry without redirection
269+
/// SlowLane: Invalid handle
270+
/// or
271+
/// SlowLane: Cannot resolve stub entry without redirection
253272
/// </exception>
254273
public nint Resolve(MemoryHandle handle)
255274
{
@@ -276,7 +295,7 @@ public nint Resolve(MemoryHandle handle)
276295

277296
/// <inheritdoc />
278297
/// <summary>
279-
/// Frees the specified handle.
298+
/// Frees the specified handle.
280299
/// </summary>
281300
/// <param name="handle">The handle.</param>
282301
/// <exception cref="T:System.InvalidOperationException">SlowLane: Invalid handle</exception>
@@ -292,6 +311,11 @@ public void Free(MemoryHandle handle)
292311
if (!_handleIndex.TryRemove(handle.Id, out var index))
293312
throw new InvalidOperationException($"SlowLane: Invalid handle {handle.Id}");
294313

314+
var entry = _entries[index];
315+
316+
// Reclaim the physical block space inside the main arena tracker!
317+
MemoryLaneUtils.ReturnFreeSpace(entry.Offset, entry.Size, ref _freeBlocks, ref _freeBlockCount);
318+
295319
_entries[index] = default;
296320
_freeSlots.Push(index);
297321
_freeIds.Push(handle.Id);
@@ -306,12 +330,13 @@ public void Free(MemoryHandle handle)
306330
/// </summary>
307331
/// <param name="handles">The handles.</param>
308332
/// <exception cref="InvalidOperationException">SlowLane: Invalid handle {handle.Id}</exception>
309-
public unsafe void FreeMany(MemoryHandle[] handles) // Or ReadOnlySpan<MemoryHandle>
333+
public unsafe void FreeMany(MemoryHandle[] handles)
310334
{
311335
var span = handles.AsSpan();
312336
var count = span.Length;
313337

314-
// We'll collect the IDs and Indices in temporary buffers to batch-push
338+
if (count == 0) return;
339+
315340
// Using stackalloc for small-to-medium batches avoids GC pressure
316341
var ids = stackalloc int[count];
317342
var indices = stackalloc int[count];
@@ -320,9 +345,21 @@ public unsafe void FreeMany(MemoryHandle[] handles) // Or ReadOnlySpan<MemoryHan
320345
{
321346
var id = span[i].Id;
322347

348+
// Route tiny blobs safely away from structural array paths
349+
if (id <= BlobManager.StartingId && _blobManager != null)
350+
{
351+
_blobManager.Free(span[i]);
352+
continue;
353+
}
354+
323355
if (!_handleIndex.TryRemove(id, out var index))
324356
throw new InvalidOperationException($"SlowLane: Invalid handle {id}");
325357

358+
var entry = _entries[index];
359+
360+
// Return the space to the physical free list for EVERY element in the batch
361+
MemoryLaneUtils.ReturnFreeSpace(entry.Offset, entry.Size, ref _freeBlocks, ref _freeBlockCount);
362+
326363
// Clear entry data
327364
_entries[index] = default;
328365

@@ -333,8 +370,6 @@ public unsafe void FreeMany(MemoryHandle[] handles) // Or ReadOnlySpan<MemoryHan
333370
// Batch add to our unmanaged lists
334371
_freeIds.PushRange(new ReadOnlySpan<int>(ids, count));
335372
_freeSlots.PushRange(new ReadOnlySpan<int>(indices, count));
336-
337-
EntryCount += count;
338373
}
339374

340375
/// <inheritdoc />
@@ -402,7 +437,7 @@ public unsafe void Compact()
402437
EntryCount = writeIndex;
403438
_freeSlots.Clear(); // No more holes in the array!
404439

405-
// 6. THE MISSING FIX: Reset the Free-List!
440+
// 6. Reset the Free-List!
406441
_freeBlocks[0] = new FreeBlock
407442
{
408443
Offset = offset,
@@ -481,9 +516,8 @@ public IEnumerable<MemoryHandle> GetHandles()
481516
}
482517

483518
/// <summary>
484-
/// Gets the used.
519+
/// Gets the used memory.
485520
/// </summary>
486-
/// <returns>Get used Id.</returns>
487521
[MethodImpl(MethodImplOptions.AggressiveInlining)]
488522
private int GetUsed()
489523
{
@@ -499,24 +533,49 @@ private int GetUsed()
499533
public int FreeSpace()
500534
{
501535
var mainFreeSpace = MemoryLaneUtils.CalculateFreeSpace(_entries, EntryCount, Capacity);
502-
503536
var blobFreeSpace = _blobManager?.FreeSpace() ?? 0;
504537

505538
return mainFreeSpace + blobFreeSpace;
506539
}
507540

508541
/// <summary>
509-
/// Ensures the entry capacity.
542+
/// Ensures the entry capacity.
510543
/// </summary>
511-
/// <param name="requiredSlotIndex">Index of the required slot.</param>
512544
private void EnsureEntryCapacity(int requiredSlotIndex)
513545
{
514546
var oldSize = _entries.Length;
515547
var newSize = MemoryLaneUtils.EnsureEntryCapacity(ref _entries, requiredSlotIndex);
516-
// Allocation Entries must be extended
548+
549+
// Defensive Safeguard: Grow free blocks buffer concurrently
550+
if (_freeBlocks.Length < newSize)
551+
{
552+
Array.Resize(ref _freeBlocks, newSize);
553+
}
554+
517555
OnAllocationExtension?.Invoke(nameof(SlowLane), oldSize, newSize);
518556
}
519557

558+
/// <summary>
559+
/// Resizes the unmanaged versions buffer.
560+
/// </summary>
561+
[MethodImpl(MethodImplOptions.NoInlining)]
562+
private unsafe void GrowVersions(int minCapacity)
563+
{
564+
var newCapacity = _versionsCapacity * 2;
565+
if (newCapacity < minCapacity) newCapacity = minCapacity;
566+
567+
var newVersions = (uint*)NativeMemory.AllocZeroed((nuint)newCapacity, sizeof(uint));
568+
569+
if (_versions != null)
570+
{
571+
Unsafe.CopyBlock(newVersions, _versions, (uint)(_versionsCapacity * sizeof(uint)));
572+
NativeMemory.Free(_versions);
573+
}
574+
575+
_versions = newVersions;
576+
_versionsCapacity = newCapacity;
577+
}
578+
520579
/// <inheritdoc />
521580
public int StubCount()
522581
{
@@ -545,19 +604,17 @@ public string DebugVisualMap()
545604
/// <inheritdoc />
546605
public string DebugRedirections()
547606
{
548-
if (_entries == null) throw new InvalidOperationException("FastLane: Invalid memory.");
607+
if (_entries == null) throw new InvalidOperationException("SlowLane: Invalid memory.");
549608

550609
#if DEBUG
551-
// Pass the debug names dictionary in Debug mode
552610
return MemoryLaneUtils.DebugRedirections(_entries, EntryCount, _debugNames);
553611
#else
554-
// Pass null in Release mode since the dictionary doesn't exist
555-
return MemoryLaneUtils.DebugRedirections(_entries, EntryCount, null);
612+
return MemoryLaneUtils.DebugRedirections(_entries, EntryCount, null);
556613
#endif
557614
}
558615

559616
/// <summary>
560-
/// Dump all Debug Infos.
617+
/// Dump all Debug Infos.
561618
/// </summary>
562619
public void LogDump()
563620
{

0 commit comments

Comments
 (0)