Skip to content

Commit d5a702b

Browse files
author
LoneWandererProductions
committed
optimize find freespot.
1 parent 7608d98 commit d5a702b

7 files changed

Lines changed: 156 additions & 38 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* COPYRIGHT: See COPYING in the top level directory
3+
* PROJECT: MemoryArenaPrototype.Core
4+
* FILE: AllocationStrategy.cs
5+
* PURPOSE: Defines the search algorithm used to locate available unmanaged space within the free-lists.
6+
* PROGRAMMER: Peter Geinitz (Wayfarer)
7+
*/
8+
9+
namespace MemoryManager.Core
10+
{
11+
/// <summary>
12+
/// Defines the search algorithm used to locate available unmanaged space within the free-lists.
13+
/// </summary>
14+
public enum AllocationStrategy : byte
15+
{
16+
/// <summary>
17+
/// Blazing fast execution. Grabs the very first block that fits. Ideal for uniform/homogeneous lifetimes.
18+
/// </summary>
19+
FirstFit,
20+
21+
/// <summary>
22+
/// Minimizes fragmentation. Scans the free-list to find the absolute smallest hole that fits the payload.
23+
/// </summary>
24+
BestFit
25+
}
26+
}

MemoryManager.Core/MemoryManagerConfig.cs

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66
* PROGRAMMER: Peter Geinitz (Wayfarer)
77
*/
88

9+
using System;
10+
911
namespace MemoryManager.Core
1012
{
1113
/// <summary>
1214
/// The config for the mm
1315
/// </summary>
1416
public sealed class MemoryManagerConfig
1517
{
16-
1718
/// <summary>
1819
/// Parameter-less constructor with defaults
1920
/// </summary>
@@ -50,8 +51,22 @@ public MemoryManagerConfig(int slowLaneSize)
5051
// Default hybrid tuning
5152
SlowLaneBlobCapacityFraction = 0.20;
5253
SlowLaneBlobThreshold = 256;
54+
55+
// Default performance-tuned layout choices
56+
FastLaneFreeListStrategy = AllocationStrategy.FirstFit;
57+
SlowLaneFreeListStrategy = AllocationStrategy.BestFit;
5358
}
5459

60+
/// <summary>
61+
/// Gets the free-list block search strategy used by the FastLane when running the FreeList strategy wrapper.
62+
/// </summary>
63+
public AllocationStrategy FastLaneFreeListStrategy { get; init; } = AllocationStrategy.FirstFit;
64+
65+
/// <summary>
66+
/// Gets the free-list block search strategy used by the SlowLane to manage gap allocations.
67+
/// </summary>
68+
public AllocationStrategy SlowLaneFreeListStrategy { get; init; } = AllocationStrategy.BestFit;
69+
5570
/// <summary>
5671
/// Gets the fast lane strategy.
5772
/// Your pick old FastLane or the more speedy BumbLane.
@@ -199,12 +214,11 @@ public MemoryManagerConfig(int slowLaneSize)
199214
public int SlowLaneBlobThreshold { get; init; } = 256;
200215

201216
/// <summary>
202-
/// Estimates the total reserved unmanaged memory (in bytes) this configuration will request,
203-
/// not including minor overhead for handles and management structures.
217+
/// Estimates the total reserved unmanaged memory (in bytes) this configuration will request,
218+
/// not including minor overhead for handles and management structures.
204219
/// </summary>
205220
public double GetEstimatedReservedMegabytes()
206221
{
207-
// BufferSize is gone! Just the raw unmanaged lane allocations.
208222
return (FastLaneSize + SlowLaneSize) / (1024.0 * 1024.0);
209223
}
210224

@@ -213,28 +227,35 @@ public double GetEstimatedReservedMegabytes()
213227
*/
214228

215229
/// <summary>
216-
/// Creates a configuration tuned for real-time game loops.
230+
/// Creates a configuration tuned for real-time game loops.
217231
/// Employs an ultra-fast Bump/Linear allocator for short-lived transient frames.
218232
/// </summary>
233+
/// <param name="totalBudget">The total budget.</param>
234+
/// <returns>MemoryManagerConfig instance configured for real-time game loops.</returns>
219235
public static MemoryManagerConfig CreateForGameLoop(int totalBudget = 16 * 1024 * 1024)
220236
{
221237
return new MemoryManagerConfig
222238
{
223239
SlowLaneSize = (int)(totalBudget * 0.75),
224240
FastLaneSize = (int)(totalBudget * 0.25),
225241
FastLaneStrategy = AllocatorStrategy.LinearBump, // Maximum speed
226-
MaxFastLaneAgeFrames = 300, // Evict to SlowLane faster
242+
MaxFastLaneAgeFrames = 300, // Evict to SlowLane faster
227243
FastLaneUsageThreshold = 0.85,
228244
CompactionThreshold = 0.75,
229245
EnableAutoCompaction = true,
230-
PolicyCheckInterval = TimeSpan.FromMilliseconds(16) // Check roughly every frame at 60fps
246+
PolicyCheckInterval = TimeSpan.FromMilliseconds(16), // Check roughly every frame at 60fps
247+
248+
// Homogeneous loop presets: SlowLane aggregates long-lived chunks neatly via BestFit
249+
SlowLaneFreeListStrategy = AllocationStrategy.BestFit
231250
};
232251
}
233252

234253
/// <summary>
235254
/// Creates a configuration tuned for heavy I/O, networking, or asset streaming.
236255
/// Uses a FreeList allocator to handle random out-of-order allocations.
237256
/// </summary>
257+
/// <param name="totalBudget">The total budget.</param>
258+
/// <returns>MemoryManagerConfig instance configured for bulk processing.</returns>
238259
public static MemoryManagerConfig CreateForBulkProcessing(int totalBudget = 64 * 1024 * 1024)
239260
{
240261
return new MemoryManagerConfig
@@ -246,27 +267,36 @@ public static MemoryManagerConfig CreateForBulkProcessing(int totalBudget = 64 *
246267
MaxFastLaneAgeFrames = 1200, // Let data sit longer before moving
247268
SlowLaneUsageThreshold = 0.80,
248269
SlowLaneBlobThreshold = 512, // Route larger fragments to blobs
249-
PolicyCheckInterval = TimeSpan.FromSeconds(2) // Low maintenance overhead
270+
PolicyCheckInterval = TimeSpan.FromSeconds(2), // Low maintenance overhead
271+
272+
// Heterogeneous presets: prioritize high allocation velocity on FastLane, anti-fragmentation on SlowLane
273+
FastLaneFreeListStrategy = AllocationStrategy.FirstFit,
274+
SlowLaneFreeListStrategy = AllocationStrategy.BestFit
250275
};
251276
}
252277

253278
/// <summary>
254279
/// Creates a configuration tuned for tightly constrained or embedded footprints.
255280
/// Minimizes unmanaged allocation limits and compacts aggressively.
256281
/// </summary>
282+
/// <returns>MemoryManagerConfig instance configured for low memory scenarios.</returns>
257283
public static MemoryManagerConfig CreateForLowMemory()
258284
{
259285
return new MemoryManagerConfig
260286
{
261-
FastLaneSize = 256 * 1024, // 256 KB
262-
SlowLaneSize = 1024 * 1024, // 1 MB
287+
FastLaneSize = 256 * 1024, // 256 KB
288+
SlowLaneSize = 1024 * 1024, // 1 MB
263289
FastLaneStrategy = AllocatorStrategy.FreeList,
264290
EnableAutoCompaction = true,
265291
CompactionThreshold = 0.60, // Compact very early
266292
FastLaneUsageThreshold = 0.70,
267293
SlowLaneUsageThreshold = 0.70,
268294
SlowLaneBlobCapacityFraction = 0.35, // Allocate more space for tiny fragments
269-
PolicyCheckInterval = TimeSpan.FromMilliseconds(500)
295+
PolicyCheckInterval = TimeSpan.FromMilliseconds(500),
296+
297+
// Low-Footprint presets: Use BestFit everywhere to squeeze the max data out of limited space limits
298+
FastLaneFreeListStrategy = AllocationStrategy.BestFit,
299+
SlowLaneFreeListStrategy = AllocationStrategy.BestFit
270300
};
271301
}
272302
}

MemoryManager.Lanes/FastLane.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,18 +78,27 @@ public sealed class FastLane : IFastLane, IDisposable
7878
/// </summary>
7979
private int _freeBlockCount;
8080

81+
/// <summary>
82+
/// The configured search strategy used to scan the free-list for gaps.
83+
/// </summary>
84+
private readonly AllocationStrategy _searchStrategy;
85+
8186
/// <summary>
8287
/// Initializes a new instance of the <see cref="FastLane" /> class.
8388
/// </summary>
8489
/// <param name="size">The size.</param>
8590
/// <param name="slowLane">The slow lane.</param>
8691
/// <param name="maxEntries">The maximum entries.</param>
87-
public unsafe FastLane(int size, SlowLane slowLane, int maxEntries = 1024)
92+
/// <param name="fastLaneFreeListStrategy">The fast lane free list strategy.</param>
93+
public unsafe FastLane(int size, SlowLane slowLane, int maxEntries = 1024, AllocationStrategy fastLaneFreeListStrategy = default)
8894
{
8995
_slowLane = slowLane;
9096
Capacity = size;
9197
Buffer = Marshal.AllocHGlobal(size);
9298

99+
// CAPTURE STRATEGY: Persist the configuration choice for all future allocation passes
100+
_searchStrategy = fastLaneFreeListStrategy;
101+
93102
// PRE-ALLOCATE everything based on maxEntries
94103
_entries = new AllocationEntry[maxEntries];
95104
_freeBlocks = new FreeBlock[maxEntries]; // Free blocks can theoretically equal maxEntries
@@ -179,7 +188,7 @@ public unsafe MemoryHandle Allocate(
179188

180189
// Calculate total layout footprint including canary guards
181190
int physicalSizeNeeded = MemoryCanary.GetPhysicalSize(size);
182-
var physicalOffset = MemoryLaneUtils.FindFreeSpot(physicalSizeNeeded, ref _freeBlocks, ref _freeBlockCount);
191+
var physicalOffset = MemoryLaneUtils.FindFreeSpot(physicalSizeNeeded, ref _freeBlocks, ref _freeBlockCount, _searchStrategy);
183192

184193
if (physicalOffset == -1)
185194
throw new OutOfMemoryException("FastLane: Cannot allocate - No contiguous block large enough.");

MemoryManager.Lanes/MemoryLaneUtils.cs

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -89,37 +89,74 @@ internal static int StubCount(ReadOnlySpan<AllocationEntry> entries)
8989
}
9090

9191
/// <summary>
92-
/// Finds a free spot using a Free-List approach (Zero Allocation, O(N) over holes, not allocations)
92+
/// Finds a free spot using a configurable Free-List search approach (Zero Allocation).
9393
/// </summary>
94-
/// <param name="size">The size.</param>
95-
/// <param name="freeBlocks">The free blocks.</param>
96-
/// <param name="freeBlockCount">The free block count.</param>
97-
/// <returns>The offset of the allocated spot, or -1 if out of memory.</returns>
94+
/// <param name="size">The physical size needed (including canaries/alignment padding).</param>
95+
/// <param name="freeBlocks">The free blocks tracker array.</param>
96+
/// <param name="freeBlockCount">The active free block count.</param>
97+
/// <param name="strategy">The allocation strategy layout constraint to use.</param>
98+
/// <returns>First free next offset for the allocator, or -1 if Out of Memory.</returns>
9899
[MethodImpl(MethodImplOptions.AggressiveInlining)]
99-
internal static int FindFreeSpot(int size, ref FreeBlock[] freeBlocks, ref int freeBlockCount)
100+
internal static int FindFreeSpot(int size, ref FreeBlock[] freeBlocks, ref int freeBlockCount, AllocationStrategy strategy)
100101
{
101-
for (var i = 0; i < freeBlockCount; i++)
102+
var targetIndex = -1;
103+
104+
switch (strategy)
102105
{
103-
if (freeBlocks[i].Size >= size)
104-
{
105-
var assignedOffset = freeBlocks[i].Offset;
106+
case AllocationStrategy.FirstFit:
107+
// --- FIRST FIT STRATEGY (High-Velocity Homogeneous Path) ---
108+
for (var i = 0; i < freeBlockCount; i++)
109+
{
110+
if (freeBlocks[i].Size >= size)
111+
{
112+
targetIndex = i;
113+
break; // Exit instantly on the first validation match
114+
}
115+
}
116+
break;
106117

107-
// Shrink the hole
108-
freeBlocks[i].Offset += size;
109-
freeBlocks[i].Size -= size;
118+
case AllocationStrategy.BestFit:
119+
// --- BEST FIT STRATEGY (Anti-Fragmentation Heterogeneous Path) ---
120+
var minRemainder = int.MaxValue;
110121

111-
// If hole is fully consumed, remove it by swapping with the last item
112-
if (freeBlocks[i].Size == 0)
122+
for (var i = 0; i < freeBlockCount; i++)
113123
{
114-
freeBlockCount--;
115-
freeBlocks[i] = freeBlocks[freeBlockCount];
124+
if (freeBlocks[i].Size >= size)
125+
{
126+
int remainder = freeBlocks[i].Size - size;
127+
128+
// Track the hole that leaves behind the smallest possible wasted remnant
129+
if (remainder < minRemainder)
130+
{
131+
minRemainder = remainder;
132+
targetIndex = i;
133+
134+
// OPTIMIZATION: If we hit a perfect structural match, stop scanning immediately
135+
if (remainder == 0) break;
136+
}
137+
}
116138
}
139+
break;
140+
}
117141

118-
return assignedOffset;
119-
}
142+
// If no suitable memory block hole was found across the free-list table
143+
if (targetIndex == -1) return -1;
144+
145+
// --- CONSUME AND UPDATE THE TRACKED HOLE ---
146+
var assignedOffset = freeBlocks[targetIndex].Offset;
147+
148+
// Shrink the hole footprint boundaries
149+
freeBlocks[targetIndex].Offset += size;
150+
freeBlocks[targetIndex].Size -= size;
151+
152+
// If the hole is fully consumed down to 0 bytes, remove it by swapping with the last item
153+
if (freeBlocks[targetIndex].Size == 0)
154+
{
155+
freeBlockCount--;
156+
freeBlocks[targetIndex] = freeBlocks[freeBlockCount];
120157
}
121158

122-
return -1; // -1 indicates Out of Memory
159+
return assignedOffset;
123160
}
124161

125162
/// <summary>

MemoryManager.Lanes/SlowLane.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ public sealed class SlowLane : IMemoryLane, IDisposable
9393
/// </summary>
9494
private int _versionsCapacity;
9595

96+
/// <summary>
97+
/// The configured search strategy used to scan the free-list for gaps.
98+
/// </summary>
99+
private readonly AllocationStrategy _searchStrategy;
100+
96101
/// <summary>
97102
/// Initializes a new instance of the <see cref="SlowLane" /> class.
98103
/// </summary>
@@ -101,11 +106,14 @@ public sealed class SlowLane : IMemoryLane, IDisposable
101106
/// <param name="blobThreshold">The BLOB threshold.</param>
102107
/// <param name="maxEntries">The maximum entries.</param>
103108
public unsafe SlowLane(int capacity, double blobCapacityFraction = 0.20, int blobThreshold = 256,
104-
int maxEntries = 1024)
109+
int maxEntries = 1024, AllocationStrategy slowLaneFreeListStrategy = default)
105110
{
106111
Capacity = capacity;
107112
_blobThreshold = blobThreshold;
108113

114+
// CAPTURE STRATEGY: Persist the configuration choice for all future allocation passes
115+
_searchStrategy = slowLaneFreeListStrategy;
116+
109117
Buffer = Marshal.AllocHGlobal(capacity);
110118
_entries = new AllocationEntry[maxEntries];
111119

@@ -192,7 +200,7 @@ public unsafe MemoryHandle Allocate(
192200

193201
// Calculate tracking footprint dimension rules including canary bytes
194202
int physicalSizeNeeded = MemoryCanary.GetPhysicalSize(size);
195-
var offset = MemoryLaneUtils.FindFreeSpot(physicalSizeNeeded, ref _freeBlocks, ref _freeBlockCount);
203+
var offset = MemoryLaneUtils.FindFreeSpot(physicalSizeNeeded, ref _freeBlocks, ref _freeBlockCount, _searchStrategy);
196204

197205
if (offset == -1)
198206
throw new OutOfMemoryException("SlowLane: Cannot allocate - No contiguous block large enough.");

MemoryManager/MemoryArena.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,19 +67,27 @@ public MemoryArena(MemoryManagerConfig config)
6767
_config = config;
6868
Threshold = config.Threshold;
6969

70-
// FIX: Pass configuration parameters to the SlowLane constructor
70+
// PASS 1: Pass the anti-fragmentation strategy flag straight into the SlowLane
7171
SlowLane = new SlowLane(
7272
config.SlowLaneSize,
7373
config.SlowLaneBlobCapacityFraction,
7474
config.SlowLaneBlobThreshold,
75-
config.MaxEntries);
75+
config.MaxEntries,
76+
config.SlowLaneFreeListStrategy); // Added strategy selection
7677

7778
if (config.FastLaneStrategy == AllocatorStrategy.FreeList)
7879
{
79-
FastLane = new FastLane(config.FastLaneSize, SlowLane, config.MaxEntries);
80+
// PASS 2: Pass the high-velocity strategy flag into the Free-List FastLane
81+
FastLane = new FastLane(
82+
config.FastLaneSize,
83+
SlowLane,
84+
config.MaxEntries,
85+
config.FastLaneFreeListStrategy); // Added strategy selection
8086
}
8187
else
8288
{
89+
// NOTE: LinearLane is a pure O(1) bump allocator. It does not scan holes,
90+
// so it does not take an AllocationStrategy configuration parameter.
8391
FastLane = new LinearLane(config.FastLaneSize, SlowLane, config.MaxEntries);
8492
}
8593

Todos.txt

-2.39 KB
Binary file not shown.

0 commit comments

Comments
 (0)