Skip to content

Commit 2ed5072

Browse files
author
LoneWandererProductions
committed
improve linear lane
1 parent 73eb777 commit 2ed5072

1 file changed

Lines changed: 74 additions & 60 deletions

File tree

MemoryManager.Lanes/LinearLane.cs

Lines changed: 74 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@
99
using ExtendedSystemObjects;
1010
using MemoryManager.Core;
1111
using System.Diagnostics;
12+
using System.Runtime.CompilerServices;
1213
using System.Runtime.InteropServices;
1314

1415
namespace MemoryManager.Lanes
1516
{
1617
/// <inheritdoc cref="IFastLane" />
1718
/// <summary>
18-
/// LinearLane with Bump Allocator
19+
/// LinearLane with Bump Allocator
1920
/// </summary>
2021
/// <seealso cref="MemoryManager.Lanes.IFastLane" />
2122
/// <seealso cref="System.IDisposable" />
@@ -59,31 +60,43 @@ public sealed class LinearLane : IFastLane, IDisposable
5960
private int _nextFreeOffset;
6061

6162
/// <summary>
62-
/// The versions
63+
/// Pointer to the flat versions array. Index matches handle ID directly.
6364
/// </summary>
64-
private readonly byte[] _versions;
65+
private unsafe uint* _versions;
66+
67+
/// <summary>
68+
/// Current capacity of the versions array.
69+
/// </summary>
70+
private int _versionsCapacity;
71+
72+
/// <inheritdoc />
73+
public event Action<string>? OnCompaction;
74+
75+
/// <inheritdoc />
76+
public event Action<string, int, int>? OnAllocationExtension;
6577

6678
/// <summary>
6779
/// Initializes a new instance of the <see cref="LinearLane"/> class.
6880
/// </summary>
6981
/// <param name="size">The size.</param>
7082
/// <param name="slowLane">The slow lane.</param>
7183
/// <param name="maxEntries">The maximum entries.</param>
72-
public LinearLane(int size, SlowLane slowLane, int maxEntries = 1024)
84+
public unsafe LinearLane(int size, SlowLane slowLane, int maxEntries = 1024)
7385
{
7486
_slowLane = slowLane;
7587
Capacity = size;
7688
Buffer = Marshal.AllocHGlobal(size);
77-
_versions = new byte[maxEntries];
89+
90+
// INITIALIZATION: Allocate flat tracking for versions matching system high-water metrics
91+
_versionsCapacity = maxEntries + 1;
92+
_versions = (uint*)NativeMemory.AllocZeroed((nuint)_versionsCapacity, sizeof(uint));
93+
7894
_entries = new AllocationEntry[maxEntries];
7995
}
8096

8197
/// <summary>
8298
/// Gets the capacity.
8399
/// </summary>
84-
/// <value>
85-
/// The capacity.
86-
/// </value>
87100
public int Capacity { get; }
88101

89102
/// <inheritdoc />
@@ -92,59 +105,61 @@ public LinearLane(int size, SlowLane slowLane, int maxEntries = 1024)
92105
/// <summary>
93106
/// Gets the buffer.
94107
/// </summary>
95-
/// <value>
96-
/// The buffer.
97-
/// </value>
98108
public nint Buffer { get; private set; }
99109

100110
/// <inheritdoc />
101111
public OneWayLane? OneWayLane { get; set; }
102112

103-
/// <summary>
104-
/// Gets the redirects.
105-
/// </summary>
106-
/// <value>
107-
/// The redirects.
108-
/// </value>
109-
private Dictionary<int, MemoryHandle> Redirects { get; } = new();
110-
111113
/// <inheritdoc />
112-
public void Dispose()
114+
public unsafe void Dispose()
113115
{
114116
Marshal.FreeHGlobal(Buffer);
115117
_handleIndex.Clear();
116-
Redirects.Clear();
118+
119+
if (_versions != null)
120+
{
121+
NativeMemory.Free(_versions);
122+
_versions = null;
123+
}
124+
117125
_entries = null;
126+
GC.SuppressFinalize(this);
118127
}
119128

120129
/// <inheritdoc />
121130
public bool CanAllocate(int size) => _nextFreeOffset + size <= Capacity;
122131

123132
/// <inheritdoc />
124-
public MemoryHandle Allocate(int size, AllocationPriority priority = AllocationPriority.Normal,
133+
public unsafe MemoryHandle Allocate(int size, AllocationPriority priority = AllocationPriority.Normal,
125134
AllocationHints hints = AllocationHints.None, string? debugName = null, int currentFrame = 0)
126135
{
127136
if (_entries == null) throw new InvalidOperationException("LinearLane: Memory not reserved");
128137
if (!CanAllocate(size))
129138
throw new OutOfMemoryException("LinearLane: Cannot allocate - Buffer is full. Requires Compaction.");
130139

131140
var offset = _nextFreeOffset;
132-
_nextFreeOffset += size; // BUMP!
141+
_nextFreeOffset += size;
133142

134143
EnsureEntryCapacity(EntryCount);
135144
var id = MemoryLaneUtils.GetNextId(_freeIds, ref _nextHandleId);
136145

137146
#if DEBUG
138147
if (!string.IsNullOrEmpty(debugName)) _debugNames[id] = debugName;
139148
#endif
140-
_versions[id % _versions.Length]++;
141-
var currentVersion = _versions[id % _versions.Length];
149+
// Dynamically scale version buffer if recycled handle IDs push past high-water thresholds
150+
if (id >= _versionsCapacity)
151+
{
152+
GrowVersions(id + 1);
153+
}
154+
155+
uint currentVersion = ++_versions[id];
142156

143157
_entries[EntryCount] = new AllocationEntry
144158
{
145159
Offset = offset,
146160
Size = size,
147161
HandleId = id,
162+
Version = currentVersion,
148163
Priority = priority,
149164
Hints = hints,
150165
RedirectToId = 0,
@@ -167,10 +182,17 @@ public nint Resolve(MemoryHandle handle)
167182

168183
ref readonly var entry = ref _entries[index];
169184

170-
if (!entry.IsStub || entry.RedirectToId == 0) return Buffer + entry.Offset;
185+
if (entry.Version != handle.Version)
186+
throw new AccessViolationException($"Zombie Handle: LinearLane ID {handle.Id} is stale.");
171187

172-
var slowHandle = new MemoryHandle(entry.RedirectToId, entry.RedirectVersion, _slowLane);
173-
return _slowLane.Resolve(slowHandle);
188+
// 2. REDIRECTION (Stub Logic)
189+
if (entry.IsStub && entry.RedirectToId != 0)
190+
{
191+
var slowHandle = new MemoryHandle(entry.RedirectToId, entry.RedirectVersion, _slowLane);
192+
return _slowLane.Resolve(slowHandle);
193+
}
194+
195+
return Buffer + entry.Offset;
174196
}
175197

176198
/// <inheritdoc />
@@ -261,21 +283,18 @@ public void ReplaceWithStub(MemoryHandle fastHandle, MemoryHandle slowHandle)
261283
var entry = _entries[index];
262284
entry.IsStub = true;
263285
entry.RedirectToId = slowHandle.Id;
286+
287+
entry.RedirectVersion = slowHandle.Version;
288+
264289
entry.Offset = 0;
265290
entry.Size = 0;
266291

267292
_entries[index] = entry;
268-
Redirects[fastHandle.Id] = slowHandle;
269293
}
270294

271295
/// <summary>
272296
/// Should move to slow lane.
273297
/// </summary>
274-
/// <param name="entry">The entry.</param>
275-
/// <param name="currentFrame">The current frame.</param>
276-
/// <param name="maxAgeFrames">The maximum age frames.</param>
277-
/// <param name="largeThreshold">The large threshold.</param>
278-
/// <returns></returns>
279298
private bool ShouldMoveToSlowLane(in AllocationEntry entry, int currentFrame, int maxAgeFrames,
280299
int largeThreshold)
281300
{
@@ -289,7 +308,6 @@ private bool ShouldMoveToSlowLane(in AllocationEntry entry, int currentFrame, in
289308
/// <summary>
290309
/// Ensures the entry capacity.
291310
/// </summary>
292-
/// <param name="requiredSlotIndex">Index of the required slot.</param>
293311
private void EnsureEntryCapacity(int requiredSlotIndex)
294312
{
295313
var oldSize = _entries!.Length;
@@ -321,23 +339,10 @@ public int EstimateFragmentation()
321339
return (int)((double)(allocatedBytes - usedBytes) / allocatedBytes * 100);
322340
}
323341

324-
/// <summary>
325-
/// Usages the percentage.
326-
/// </summary>
327-
/// <returns>
328-
/// Used memory Percentage
329-
/// </returns>
342+
/// <inheritdoc />
330343
public double UsagePercentage() => (double)(Capacity - FreeSpace()) / Capacity;
331344

332345
/// <inheritdoc />
333-
/// <summary>
334-
/// Retrieves the full allocation entry metadata for a given handle.
335-
/// Passthroughs to MemoryLaneUtils
336-
/// </summary>
337-
/// <param name="handle">The handle identifying the allocation.</param>
338-
/// <returns>
339-
/// The allocation entry associated with the handle.
340-
/// </returns>
341346
public AllocationEntry GetEntry(MemoryHandle handle) =>
342347
MemoryLaneUtils.GetEntry(handle, _handleIndex, _entries!, nameof(LinearLane));
343348

@@ -362,29 +367,38 @@ public IEnumerable<MemoryHandle> GetHandles()
362367
{
363368
if (_entries == null) yield break;
364369

365-
// We must iterate the keys and look up the version in the metadata
366370
foreach (var id in _handleIndex.Keys)
367371
{
368372
if (_handleIndex.TryGetValue(id, out var index))
369373
{
370-
// Grab the generation from the actual entry
371374
var version = _entries[index].Version;
372-
373-
// Return the "Smart Handle" with its proof-of-life
374375
yield return new MemoryHandle(id, version, this);
375376
}
376377
}
377378
}
378379

379-
/// <inheritdoc />
380-
public event Action<string>? OnCompaction;
381-
382-
/// <inheritdoc />
383-
public event Action<string, int, int>? OnAllocationExtension;
384-
385380
/// <summary>
386-
/// Logs the dump.
381+
/// Resizes the unmanaged versions buffer.
387382
/// </summary>
383+
[MethodImpl(MethodImplOptions.NoInlining)]
384+
private unsafe void GrowVersions(int minCapacity)
385+
{
386+
var newCapacity = _versionsCapacity * 2;
387+
if (newCapacity < minCapacity) newCapacity = minCapacity;
388+
389+
var newVersions = (uint*)NativeMemory.AllocZeroed((nuint)newCapacity, sizeof(uint));
390+
391+
if (_versions != null)
392+
{
393+
Unsafe.CopyBlock(newVersions, _versions, (uint)(_versionsCapacity * sizeof(uint)));
394+
NativeMemory.Free(_versions);
395+
}
396+
397+
_versions = newVersions;
398+
_versionsCapacity = newCapacity;
399+
}
400+
401+
/// <inheritdoc />
388402
public void LogDump() => Trace.WriteLine(DebugDump());
389403
}
390404
}

0 commit comments

Comments
 (0)