Files
Misaki.HighPerformance/Misaki.HighPerformance.Test/UnitTest/Buffer/TestFreeList.cs
Misaki 28e921c48d feat(buffer)!: refactor allocators to use MemoryPool<T>
Refactor memory allocation system to use generic MemoryPool<TAllocator, TOpts> for arena, stack, and free list allocators, replacing custom allocator structs. Introduce MemoryBlock as a safer, more robust replacement for UnTypedArray. Improve thread safety, safety checks, and documentation. Reorder and clarify Allocator enum. Add comprehensive unit tests for all allocators and pointer assertion utilities. Update project to enable safety checks in Debug builds. Remove obsolete interfaces and ensure consistent deallocation with MemoryUtility.Free.

BREAKING CHANGE: Custom allocator structs are removed and replaced with MemoryPool-based abstraction. UnTypedArray is replaced by MemoryBlock. Allocator enum order and semantics are changed. Public API changes may require code updates.
2026-04-04 19:24:02 +09:00

181 lines
4.8 KiB
C#

using Misaki.HighPerformance.LowLevel.Buffer;
namespace Misaki.HighPerformance.Test.UnitTest.Buffer;
[TestClass]
public unsafe class TestFreeList
{
[TestMethod]
public void SingleThreadedAllocFreeTest()
{
using var freeList = new FreeList(8, 1024);
// Allocate various sizes
var p1 = freeList.Allocate(16, 8);
var p2 = freeList.Allocate(32, 8);
var p3 = freeList.Allocate(64, 8);
Assert.IsNotNull(p1);
Assert.IsNotNull(p2);
Assert.IsNotNull(p3);
// Free them
freeList.Free(p1);
freeList.Free(p2);
freeList.Free(p3);
// Allocate again - should reuse from buckets (or at least succeed)
var p4 = freeList.Allocate(16, 8);
var p5 = freeList.Allocate(32, 8);
Assert.IsNotNull(p4);
Assert.IsNotNull(p5);
freeList.Free(p4);
freeList.Free(p5);
}
[TestMethod]
public void MultiThreadedAllocSameThreadFreeTest()
{
const int threadCount = 8;
const int iterations = 1000;
using var freeList = new FreeList(8, 64 * 1024, threadCount);
var threads = new Thread[threadCount];
for (var i = 0; i < threadCount; i++)
{
threads[i] = new Thread(() =>
{
for (var j = 0; j < iterations; j++)
{
var ptr = freeList.Allocate(16, 8);
Assert.IsNotNull(ptr);
freeList.Free(ptr);
}
});
}
foreach (var t in threads)
t.Start();
foreach (var t in threads)
t.Join();
}
[TestMethod]
public void MultiThreadedCrossThreadFreeTest()
{
const int producerCount = 4;
const int consumerCount = 4;
const int iterations = 5000;
using var freeList = new FreeList(8, 64 * 1024, producerCount + consumerCount);
var queue = new System.Collections.Concurrent.ConcurrentQueue<IntPtr>();
var producers = new Thread[producerCount];
var consumers = new Thread[consumerCount];
var producing = true;
for (var i = 0; i < producerCount; i++)
{
producers[i] = new Thread(() =>
{
for (var j = 0; j < iterations; j++)
{
var ptr = freeList.Allocate(32, 8);
Assert.IsNotNull(ptr);
queue.Enqueue((IntPtr)ptr);
}
});
}
for (var i = 0; i < consumerCount; i++)
{
consumers[i] = new Thread(() =>
{
while (Volatile.Read(ref producing) || !queue.IsEmpty)
{
if (queue.TryDequeue(out var ptr))
{
freeList.Free((void*)ptr);
}
else
{
Thread.Yield();
}
}
});
}
foreach (var t in producers)
t.Start();
foreach (var t in consumers)
t.Start();
foreach (var t in producers)
t.Join();
Volatile.Write(ref producing, false);
foreach (var t in consumers)
t.Join();
}
[TestMethod]
public void OverflowCacheTest()
{
// Set maxConcurrencyLevel to 1, but use more threads
const int threadCount = 5;
using var freeList = new FreeList(8, 1024, 1);
var threads = new Thread[threadCount];
for (var i = 0; i < threadCount; i++)
{
threads[i] = new Thread(() =>
{
var ptr = freeList.Allocate(16, 8);
Assert.IsNotNull(ptr);
freeList.Free(ptr);
});
}
foreach (var t in threads)
t.Start();
foreach (var t in threads)
t.Join();
}
[TestMethod]
public void LargeAllocationTest()
{
using var freeList = new FreeList(8, 1024);
// Allocate larger than default chunk size
nuint largeSize = 2048;
var ptr = freeList.Allocate(largeSize, 8);
Assert.IsNotNull(ptr);
freeList.Free(ptr);
}
[TestMethod]
public void ZeroSizeAllocation_ReturnsNull()
{
using var freeList = new FreeList(8, 1024);
Assert.IsNull(freeList.Allocate(0, 8));
}
[TestMethod]
public void InvalidAlignment_Throws()
{
using var freeList = new FreeList(8, 1024);
Assert.ThrowsExactly<ArgumentException>(() => freeList.Allocate(16, 3));
}
[TestMethod]
public void DoubleDispose_IsSafe()
{
var freeList = new FreeList(8, 1024);
freeList.Dispose();
freeList.Dispose(); // Should not throw
}
}