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.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
|
||||
namespace Misaki.HighPerformance.Test.UnitTest.Buffer;
|
||||
|
||||
[TestClass]
|
||||
[DoNotParallelize]
|
||||
public class TestAllocationManager
|
||||
{
|
||||
[TestMethod]
|
||||
public void PersistentAllocationTest()
|
||||
{
|
||||
var ptr1 = new MemoryBlock(1024, 8, Allocator.Persistent);
|
||||
var ptr2 = new MemoryBlock(2048, 8, Allocator.Persistent);
|
||||
|
||||
Assert.IsTrue(ptr1.IsCreated);
|
||||
Assert.IsTrue(ptr2.IsCreated);
|
||||
|
||||
ptr1.Dispose();
|
||||
ptr2.Dispose();
|
||||
|
||||
Assert.IsFalse(ptr1.IsCreated);
|
||||
Assert.IsFalse(ptr2.IsCreated);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TempAllocationTest()
|
||||
{
|
||||
var ptr1 = new MemoryBlock(1024, 8, Allocator.Temp);
|
||||
var ptr2 = new MemoryBlock(2048, 8, Allocator.Temp);
|
||||
|
||||
Assert.IsTrue(ptr1.IsCreated);
|
||||
Assert.IsTrue(ptr2.IsCreated);
|
||||
|
||||
ptr1.Dispose();
|
||||
ptr2.Dispose();
|
||||
|
||||
Assert.IsFalse(ptr1.IsCreated);
|
||||
Assert.IsFalse(ptr2.IsCreated);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FreeListAllocationTest()
|
||||
{
|
||||
var ptr1 = new MemoryBlock(1024, 8, Allocator.FreeList);
|
||||
var ptr2 = new MemoryBlock(2048, 8, Allocator.FreeList);
|
||||
|
||||
Assert.IsTrue(ptr1.IsCreated);
|
||||
Assert.IsTrue(ptr2.IsCreated);
|
||||
|
||||
ptr1.Dispose();
|
||||
ptr2.Dispose();
|
||||
|
||||
Assert.IsFalse(ptr1.IsCreated);
|
||||
Assert.IsFalse(ptr2.IsCreated);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public unsafe void StackAllocationTest()
|
||||
{
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
var scope = AllocationManager.CreateStackScope();
|
||||
var ptr1 = new MemoryBlock(1024, 8, scope.AllocationHandle);
|
||||
|
||||
Assert.IsTrue(ptr1.IsCreated);
|
||||
Assert.AreEqual(1024u, ((VirtualStack*)scope.AllocationHandle.State)->Allocated);
|
||||
|
||||
ptr1.Dispose();
|
||||
scope.Dispose();
|
||||
Assert.AreEqual(0u, ((VirtualStack*)scope.AllocationHandle.State)->Allocated);
|
||||
});
|
||||
|
||||
thread.Start();
|
||||
|
||||
var scope = AllocationManager.CreateStackScope();
|
||||
var ptr2 = new MemoryBlock(1024, 8, scope.AllocationHandle);
|
||||
|
||||
Assert.IsTrue(ptr2.IsCreated);
|
||||
Assert.AreEqual(1024u, ((VirtualStack*)scope.AllocationHandle.State)->Allocated);
|
||||
|
||||
ptr2.Dispose();
|
||||
scope.Dispose();
|
||||
Assert.AreEqual(0u, ((VirtualStack*)scope.AllocationHandle.State)->Allocated);
|
||||
}
|
||||
}
|
||||
358
Misaki.HighPerformance.Test/UnitTest/Buffer/TestArena.cs
Normal file
358
Misaki.HighPerformance.Test/UnitTest/Buffer/TestArena.cs
Normal file
@@ -0,0 +1,358 @@
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Misaki.HighPerformance.Test.UnitTest.Buffer;
|
||||
|
||||
[TestClass]
|
||||
public unsafe class TestArena
|
||||
{
|
||||
[TestMethod]
|
||||
public void VirtualArena_AllocateReallocateResetDispose_Works()
|
||||
{
|
||||
var arena = new VirtualArena(100_000);
|
||||
try
|
||||
{
|
||||
Assert.IsGreaterThanOrEqualTo(100_000u, arena.Reserved);
|
||||
Assert.AreEqual(0u, (uint)arena.Allocated);
|
||||
|
||||
var p1 = (byte*)arena.Allocate(64, 16, AllocationOption.Clear);
|
||||
Assert.IsTrue(p1 != null);
|
||||
Assert.AreEqual((nuint)0u, ((nuint)p1) & 15u);
|
||||
|
||||
for (var i = 0; i < 64; i++)
|
||||
{
|
||||
Assert.AreEqual(0, p1[i]);
|
||||
p1[i] = 0x5A;
|
||||
}
|
||||
|
||||
var sameOnShrink = arena.Reallocate(p1, 64, 32, 16, AllocationOption.None);
|
||||
Assert.AreEqual((nint)p1, (nint)sameOnShrink);
|
||||
|
||||
var p2 = (byte*)arena.Allocate(32, 8, AllocationOption.None);
|
||||
Assert.IsTrue(p2 != null);
|
||||
|
||||
for (var i = 0; i < 64; i++)
|
||||
{
|
||||
p1[i] = (byte)(i + 1);
|
||||
}
|
||||
|
||||
var moved = (byte*)arena.Reallocate(p1, 64, 128, 16, AllocationOption.None);
|
||||
Assert.IsNotNull(moved);
|
||||
for (var i = 0; i < 64; i++)
|
||||
{
|
||||
Assert.AreEqual((byte)(i + 1), moved[i]);
|
||||
}
|
||||
|
||||
arena.Reset();
|
||||
Assert.AreEqual(0u, (uint)arena.Allocated);
|
||||
|
||||
var pAfterReset = (byte*)arena.Allocate(32, 8, AllocationOption.None);
|
||||
Assert.AreEqual((nint)arena.Buffer, (nint)pAfterReset);
|
||||
}
|
||||
finally
|
||||
{
|
||||
arena.Dispose();
|
||||
}
|
||||
|
||||
Assert.IsTrue(arena.Buffer == null);
|
||||
Assert.AreEqual(0u, (uint)arena.Reserved);
|
||||
Assert.IsNull(arena.Allocate(16, 8, AllocationOption.None));
|
||||
|
||||
// Double dispose should be safe
|
||||
arena.Dispose();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void VirtualArena_ReallocateTailExtendsInPlace_AndClearsAdditionalMemory()
|
||||
{
|
||||
var arena = new VirtualArena(128 * 1024);
|
||||
try
|
||||
{
|
||||
var ptr = (byte*)arena.Allocate(32, 8, AllocationOption.None);
|
||||
Assert.IsNotNull(ptr);
|
||||
for (var i = 0; i < 32; i++)
|
||||
ptr[i] = 0x33;
|
||||
|
||||
var grown = (byte*)arena.Reallocate(ptr, 32, 96, 8, AllocationOption.Clear);
|
||||
Assert.AreEqual((nint)ptr, (nint)grown);
|
||||
|
||||
for (var i = 0; i < 32; i++)
|
||||
Assert.AreEqual(0x33, grown[i]);
|
||||
for (var i = 32; i < 96; i++)
|
||||
Assert.AreEqual(0, grown[i]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
arena.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void VirtualArena_MultiThreadedAllocation_IsThreadSafe()
|
||||
{
|
||||
var mem = (VirtualArena*)NativeMemory.AllocZeroed((nuint)sizeof(VirtualArena));
|
||||
*mem = new VirtualArena(8 * 1024 * 1024);
|
||||
|
||||
try
|
||||
{
|
||||
const int threadCount = 8;
|
||||
const int iterations = 2000;
|
||||
var ptrs = new ConcurrentDictionary<nint, byte>();
|
||||
var errors = new ConcurrentQueue<Exception>();
|
||||
|
||||
var shared = (IntPtr)mem;
|
||||
var threads = new Thread[threadCount];
|
||||
|
||||
for (var i = 0; i < threadCount; i++)
|
||||
{
|
||||
threads[i] = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var arena = (VirtualArena*)shared;
|
||||
for (var j = 0; j < iterations; j++)
|
||||
{
|
||||
var p = (byte*)arena->Allocate(16, 8, AllocationOption.None);
|
||||
Assert.IsNotNull(p);
|
||||
Assert.AreEqual((nuint)0, ((nuint)p) & 7);
|
||||
Assert.IsTrue(ptrs.TryAdd((nint)p, 0));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors.Enqueue(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var t in threads)
|
||||
t.Start();
|
||||
foreach (var t in threads)
|
||||
t.Join();
|
||||
|
||||
if (errors.TryDequeue(out var err))
|
||||
{
|
||||
throw err;
|
||||
}
|
||||
|
||||
Assert.AreEqual(threadCount * iterations, ptrs.Count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
mem->Dispose();
|
||||
NativeMemory.Free(mem);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Arena_AllocateReallocateResetDispose_Works()
|
||||
{
|
||||
var arena = new Arena(1024);
|
||||
try
|
||||
{
|
||||
var p1 = (byte*)arena.Allocate(32, 8, AllocationOption.Clear);
|
||||
Assert.IsNotNull(p1);
|
||||
for (var i = 0; i < 32; i++)
|
||||
Assert.AreEqual(0, p1[i]);
|
||||
|
||||
for (var i = 0; i < 32; i++)
|
||||
p1[i] = 0x42;
|
||||
var same = (byte*)arena.Reallocate(p1, 32, 64, 8, AllocationOption.Clear);
|
||||
Assert.AreEqual((nint)p1, (nint)same);
|
||||
for (var i = 0; i < 32; i++)
|
||||
Assert.AreEqual(0x42, same[i]);
|
||||
for (var i = 32; i < 64; i++)
|
||||
Assert.AreEqual(0, same[i]);
|
||||
|
||||
var p2 = (byte*)arena.Allocate(16, 8, AllocationOption.None);
|
||||
Assert.IsNotNull(p2);
|
||||
|
||||
for (var i = 0; i < 64; i++)
|
||||
same[i] = (byte)(255 - i);
|
||||
var moved = (byte*)arena.Reallocate(same, 64, 96, 8, AllocationOption.None);
|
||||
Assert.IsNotNull(moved);
|
||||
for (var i = 0; i < 64; i++)
|
||||
Assert.AreEqual((byte)(255 - i), moved[i]);
|
||||
|
||||
arena.Reset();
|
||||
Assert.AreEqual(0u, (uint)arena.Offset);
|
||||
var firstAfterReset = (byte*)arena.Allocate(8, 8, AllocationOption.None);
|
||||
Assert.AreEqual((nint)arena.Buffer, (nint)firstAfterReset);
|
||||
}
|
||||
finally
|
||||
{
|
||||
arena.Dispose();
|
||||
}
|
||||
|
||||
Assert.IsTrue(arena.Buffer == null);
|
||||
Assert.AreEqual(0u, (uint)arena.Size);
|
||||
Assert.IsNull(arena.Allocate(8, 8, AllocationOption.None));
|
||||
arena.Dispose();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Arena_InvalidAlignment_Throws()
|
||||
{
|
||||
var arena = new Arena(256);
|
||||
try
|
||||
{
|
||||
Assert.ThrowsExactly<ArgumentException>(() => arena.Allocate(8, 3, AllocationOption.None));
|
||||
}
|
||||
finally
|
||||
{
|
||||
arena.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Arena_MultiThreadedAllocation_IsThreadSafe()
|
||||
{
|
||||
var mem = (Arena*)NativeMemory.AllocZeroed((nuint)sizeof(Arena));
|
||||
*mem = new Arena(4 * 1024 * 1024);
|
||||
|
||||
try
|
||||
{
|
||||
const int threadCount = 8;
|
||||
const int iterations = 2000;
|
||||
|
||||
var ptrs = new ConcurrentDictionary<nint, byte>();
|
||||
var errors = new ConcurrentQueue<Exception>();
|
||||
var shared = (IntPtr)mem;
|
||||
|
||||
var threads = new Thread[threadCount];
|
||||
for (var i = 0; i < threadCount; i++)
|
||||
{
|
||||
threads[i] = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var arena = (Arena*)shared;
|
||||
for (var j = 0; j < iterations; j++)
|
||||
{
|
||||
var p = (byte*)arena->Allocate(16, 8, AllocationOption.None);
|
||||
Assert.IsNotNull(p);
|
||||
Assert.AreEqual((nuint)0, ((nuint)p) & 7);
|
||||
Assert.IsTrue(ptrs.TryAdd((nint)p, 0));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors.Enqueue(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var t in threads)
|
||||
t.Start();
|
||||
foreach (var t in threads)
|
||||
t.Join();
|
||||
|
||||
if (errors.TryDequeue(out var err))
|
||||
{
|
||||
throw err;
|
||||
}
|
||||
|
||||
Assert.AreEqual(threadCount * iterations, ptrs.Count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
mem->Dispose();
|
||||
NativeMemory.Free(mem);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DynamicArena_GrowthReallocateResetDispose_Works()
|
||||
{
|
||||
var arena = new DynamicArena(64);
|
||||
try
|
||||
{
|
||||
var p1 = (byte*)arena.Allocate(48, 8, AllocationOption.None);
|
||||
Assert.IsNotNull(p1);
|
||||
for (var i = 0; i < 48; i++)
|
||||
p1[i] = (byte)(i + 1);
|
||||
|
||||
// Force growth to another node
|
||||
var p2 = (byte*)arena.Allocate(128, 8, AllocationOption.Clear);
|
||||
Assert.IsNotNull(p2);
|
||||
Assert.AreNotEqual((nint)p1, (nint)p2);
|
||||
for (var i = 0; i < 128; i++)
|
||||
Assert.AreEqual(0, p2[i]);
|
||||
|
||||
var moved = (byte*)arena.Reallocate(p1, 48, 96, 8, AllocationOption.None);
|
||||
Assert.IsNotNull(moved);
|
||||
for (var i = 0; i < 48; i++)
|
||||
Assert.AreEqual((byte)(i + 1), moved[i]);
|
||||
|
||||
arena.Reset();
|
||||
var firstAfterReset = (byte*)arena.Allocate(16, 8, AllocationOption.None);
|
||||
Assert.IsNotNull(firstAfterReset);
|
||||
Assert.AreEqual((nint)p1, (nint)firstAfterReset);
|
||||
}
|
||||
finally
|
||||
{
|
||||
arena.Dispose();
|
||||
}
|
||||
|
||||
Assert.IsNull(arena.Allocate(8, 8, AllocationOption.None));
|
||||
arena.Dispose();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DynamicArena_MultiThreadedAllocation_IsThreadSafe()
|
||||
{
|
||||
var mem = (DynamicArena*)NativeMemory.AllocZeroed((nuint)sizeof(DynamicArena));
|
||||
*mem = new DynamicArena(256);
|
||||
|
||||
try
|
||||
{
|
||||
const int threadCount = 8;
|
||||
const int iterations = 1000;
|
||||
|
||||
var ptrs = new ConcurrentDictionary<nint, byte>();
|
||||
var errors = new ConcurrentQueue<Exception>();
|
||||
var shared = (IntPtr)mem;
|
||||
var threads = new Thread[threadCount];
|
||||
|
||||
for (var i = 0; i < threadCount; i++)
|
||||
{
|
||||
threads[i] = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var arena = (DynamicArena*)shared;
|
||||
for (var j = 0; j < iterations; j++)
|
||||
{
|
||||
var p = (byte*)arena->Allocate(24, 8, AllocationOption.None);
|
||||
Assert.IsNotNull(p);
|
||||
Assert.AreEqual((nuint)0, ((nuint)p) & 7);
|
||||
Assert.IsTrue(ptrs.TryAdd((nint)p, 0));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors.Enqueue(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var t in threads)
|
||||
t.Start();
|
||||
foreach (var t in threads)
|
||||
t.Join();
|
||||
|
||||
if (errors.TryDequeue(out var err))
|
||||
{
|
||||
throw err;
|
||||
}
|
||||
|
||||
Assert.AreEqual(threadCount * iterations, ptrs.Count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
mem->Dispose();
|
||||
NativeMemory.Free(mem);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,9 @@ public unsafe class TestFreeList
|
||||
var p2 = freeList.Allocate(32, 8);
|
||||
var p3 = freeList.Allocate(64, 8);
|
||||
|
||||
Assert.IsTrue(p1 != null);
|
||||
Assert.IsTrue(p2 != null);
|
||||
Assert.IsTrue(p3 != null);
|
||||
Assert.IsNotNull(p1);
|
||||
Assert.IsNotNull(p2);
|
||||
Assert.IsNotNull(p3);
|
||||
|
||||
// Free them
|
||||
freeList.Free(p1);
|
||||
@@ -28,8 +28,8 @@ public unsafe class TestFreeList
|
||||
var p4 = freeList.Allocate(16, 8);
|
||||
var p5 = freeList.Allocate(32, 8);
|
||||
|
||||
Assert.IsTrue(p4 != null);
|
||||
Assert.IsTrue(p5 != null);
|
||||
Assert.IsNotNull(p4);
|
||||
Assert.IsNotNull(p5);
|
||||
|
||||
freeList.Free(p4);
|
||||
freeList.Free(p5);
|
||||
@@ -50,7 +50,7 @@ public unsafe class TestFreeList
|
||||
for (var j = 0; j < iterations; j++)
|
||||
{
|
||||
var ptr = freeList.Allocate(16, 8);
|
||||
Assert.IsTrue(ptr != null);
|
||||
Assert.IsNotNull(ptr);
|
||||
freeList.Free(ptr);
|
||||
}
|
||||
});
|
||||
@@ -83,7 +83,7 @@ public unsafe class TestFreeList
|
||||
for (var j = 0; j < iterations; j++)
|
||||
{
|
||||
var ptr = freeList.Allocate(32, 8);
|
||||
Assert.IsTrue(ptr != null);
|
||||
Assert.IsNotNull(ptr);
|
||||
queue.Enqueue((IntPtr)ptr);
|
||||
}
|
||||
});
|
||||
@@ -132,7 +132,7 @@ public unsafe class TestFreeList
|
||||
threads[i] = new Thread(() =>
|
||||
{
|
||||
var ptr = freeList.Allocate(16, 8);
|
||||
Assert.IsTrue(ptr != null);
|
||||
Assert.IsNotNull(ptr);
|
||||
freeList.Free(ptr);
|
||||
});
|
||||
}
|
||||
@@ -151,8 +151,30 @@ public unsafe class TestFreeList
|
||||
// Allocate larger than default chunk size
|
||||
nuint largeSize = 2048;
|
||||
var ptr = freeList.Allocate(largeSize, 8);
|
||||
Assert.IsTrue(ptr != null);
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
249
Misaki.HighPerformance.Test/UnitTest/Buffer/TestStack.cs
Normal file
249
Misaki.HighPerformance.Test/UnitTest/Buffer/TestStack.cs
Normal file
@@ -0,0 +1,249 @@
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
|
||||
namespace Misaki.HighPerformance.Test.UnitTest.Buffer;
|
||||
|
||||
[TestClass]
|
||||
public unsafe class TestStack
|
||||
{
|
||||
[TestMethod]
|
||||
public void Stack_ScopeAllocateReallocateResetDispose_Works()
|
||||
{
|
||||
var stack = new Stack(256 * 1024);
|
||||
try
|
||||
{
|
||||
var handle = default(AllocationHandle);
|
||||
using (stack.CreateScope(handle))
|
||||
{
|
||||
var p1 = (byte*)stack.Allocate(32, 8, AllocationOption.Clear);
|
||||
Assert.IsNotNull(p1);
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
Assert.AreEqual(0, p1[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
p1[i] = 0x11;
|
||||
}
|
||||
|
||||
var grown = (byte*)stack.Reallocate(p1, 32, 96, 8, AllocationOption.Clear);
|
||||
Assert.AreEqual((nint)p1, (nint)grown);
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
Assert.AreEqual(0x11, grown[i]);
|
||||
}
|
||||
|
||||
for (var i = 32; i < 96; i++)
|
||||
{
|
||||
Assert.AreEqual(0, grown[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Assert.AreEqual(0u, (uint)stack.Offset);
|
||||
|
||||
stack.Reset();
|
||||
Assert.AreEqual(0u, (uint)stack.Offset);
|
||||
}
|
||||
finally
|
||||
{
|
||||
stack.Dispose();
|
||||
}
|
||||
|
||||
Assert.IsNull(stack.Buffer);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Stack_NestedScopes_RewindToPreviousOffsets()
|
||||
{
|
||||
var stack = new Stack(128 * 1024);
|
||||
try
|
||||
{
|
||||
var handle = default(AllocationHandle);
|
||||
using (stack.CreateScope(handle))
|
||||
{
|
||||
var pOuter = stack.Allocate(32, 8, AllocationOption.None);
|
||||
Assert.IsNotNull(pOuter);
|
||||
var outerOffset = stack.Offset;
|
||||
|
||||
using (stack.CreateScope(handle))
|
||||
{
|
||||
var pInner = stack.Allocate(128, 8, AllocationOption.None);
|
||||
Assert.IsNotNull(pInner);
|
||||
Assert.IsTrue(stack.Offset > outerOffset);
|
||||
}
|
||||
|
||||
Assert.AreEqual(outerOffset, stack.Offset);
|
||||
}
|
||||
|
||||
Assert.AreEqual(0u, (uint)stack.Offset);
|
||||
}
|
||||
finally
|
||||
{
|
||||
stack.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Stack_MultipleAllocations_Works()
|
||||
{
|
||||
var stack = new Stack(1024 * 1024);
|
||||
try
|
||||
{
|
||||
using (stack.CreateScope(default))
|
||||
{
|
||||
var p1 = (byte*)stack.Allocate(32, 8, AllocationOption.Clear);
|
||||
Assert.IsNotNull(p1);
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
Assert.AreEqual(0, p1[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
p1[i] = 0x42;
|
||||
}
|
||||
|
||||
var same = (byte*)stack.Reallocate(p1, 32, 64, 8, AllocationOption.Clear);
|
||||
Assert.AreEqual((nint)p1, (nint)same);
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
Assert.AreEqual(0x42, same[i]);
|
||||
}
|
||||
|
||||
for (var i = 32; i < 64; i++)
|
||||
{
|
||||
Assert.AreEqual(0, same[i]);
|
||||
}
|
||||
|
||||
var p2 = (byte*)stack.Allocate(16, 8, AllocationOption.None);
|
||||
Assert.IsNotNull(p2);
|
||||
|
||||
for (var i = 0; i < 64; i++)
|
||||
{
|
||||
same[i] = (byte)(255 - i);
|
||||
}
|
||||
|
||||
var moved = (byte*)stack.Reallocate(same, 64, 96, 8, AllocationOption.None);
|
||||
Assert.IsNotNull(moved);
|
||||
for (var i = 0; i < 64; i++)
|
||||
{
|
||||
Assert.AreEqual((byte)(255 - i), moved[i]);
|
||||
}
|
||||
|
||||
stack.Reset();
|
||||
Assert.AreEqual(0u, (uint)stack.Offset);
|
||||
var firstAfterReset = (byte*)stack.Allocate(8, 8, AllocationOption.None);
|
||||
Assert.AreEqual((nint)stack.Buffer, (nint)firstAfterReset);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
stack.Dispose();
|
||||
}
|
||||
|
||||
Assert.IsNull(stack.Buffer);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Stack_AllocationFailsOutsideScope()
|
||||
{
|
||||
var stack = new Stack(128 * 1024);
|
||||
try
|
||||
{
|
||||
Assert.ThrowsExactly<InvalidOperationException>(() => stack.Allocate(32, 8, AllocationOption.Clear));
|
||||
}
|
||||
finally
|
||||
{
|
||||
stack.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Stack_InvalidAlignment_Throws()
|
||||
{
|
||||
var stack = new Stack(128 * 1024);
|
||||
try
|
||||
{
|
||||
using (stack.CreateScope(default))
|
||||
{
|
||||
Assert.ThrowsExactly<ArgumentException>(() => stack.Allocate(32, 3, AllocationOption.None));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
stack.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Stack_ZeroSizeAllocation_ReturnsNull()
|
||||
{
|
||||
var stack = new Stack(128 * 1024);
|
||||
try
|
||||
{
|
||||
using (stack.CreateScope(default))
|
||||
{
|
||||
Assert.IsNull(stack.Allocate(0, 8, AllocationOption.None));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
stack.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Stack_AllocationExceedsCapacity_Throws()
|
||||
{
|
||||
var stack = new Stack(1024);
|
||||
try
|
||||
{
|
||||
using (stack.CreateScope(default))
|
||||
{
|
||||
Assert.ThrowsExactly<OutOfMemoryException>(() => stack.Allocate(2048, 8, AllocationOption.None));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
stack.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Stack_ReallocateSmallerSize_ReturnsSamePointer()
|
||||
{
|
||||
var stack = new Stack(128 * 1024);
|
||||
try
|
||||
{
|
||||
using (stack.CreateScope(default))
|
||||
{
|
||||
var p = (byte*)stack.Allocate(64, 8);
|
||||
Assert.IsNotNull(p);
|
||||
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
p[i] = (byte)(i + 1);
|
||||
}
|
||||
|
||||
var shrunk = (byte*)stack.Reallocate(p, 64, 32, 8, AllocationOption.None);
|
||||
Assert.AreEqual((nint)p, (nint)shrunk);
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
Assert.AreEqual((byte)(i + 1), shrunk[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
stack.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Stack_DoubleDispose_IsSafe()
|
||||
{
|
||||
var stack = new Stack(128 * 1024);
|
||||
stack.Dispose();
|
||||
stack.Dispose(); // Should not throw
|
||||
}
|
||||
}
|
||||
268
Misaki.HighPerformance.Test/UnitTest/Buffer/TestVirtualStack.cs
Normal file
268
Misaki.HighPerformance.Test/UnitTest/Buffer/TestVirtualStack.cs
Normal file
@@ -0,0 +1,268 @@
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
|
||||
namespace Misaki.HighPerformance.Test.UnitTest.Buffer;
|
||||
|
||||
[TestClass]
|
||||
public unsafe class TestVirtualStack
|
||||
{
|
||||
[TestMethod]
|
||||
public void VirtualStack_ScopeAllocateReallocateResetDispose_Works()
|
||||
{
|
||||
var stack = new VirtualStack(256 * 1024);
|
||||
try
|
||||
{
|
||||
var handle = default(AllocationHandle);
|
||||
using (stack.CreateScope(handle))
|
||||
{
|
||||
var p1 = (byte*)stack.Allocate(32, 8, AllocationOption.Clear);
|
||||
Assert.IsNotNull(p1);
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
Assert.AreEqual(0, p1[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
p1[i] = 0x11;
|
||||
}
|
||||
|
||||
var grown = (byte*)stack.Reallocate(p1, 32, 96, 8, AllocationOption.Clear);
|
||||
Assert.AreEqual((nint)p1, (nint)grown);
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
Assert.AreEqual(0x11, grown[i]);
|
||||
}
|
||||
|
||||
for (var i = 32; i < 96; i++)
|
||||
{
|
||||
Assert.AreEqual(0, grown[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Assert.AreEqual(0u, (uint)stack.Allocated);
|
||||
|
||||
stack.Reset();
|
||||
Assert.AreEqual(0u, (uint)stack.Allocated);
|
||||
}
|
||||
finally
|
||||
{
|
||||
stack.Dispose();
|
||||
}
|
||||
|
||||
Assert.IsTrue(stack.Buffer == null);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void VirtualStack_NestedScopes_RewindToPreviousOffsets()
|
||||
{
|
||||
var stack = new VirtualStack(128 * 1024);
|
||||
try
|
||||
{
|
||||
var handle = default(AllocationHandle);
|
||||
using (stack.CreateScope(handle))
|
||||
{
|
||||
var pOuter = stack.Allocate(32, 8, AllocationOption.None);
|
||||
Assert.IsNotNull(pOuter);
|
||||
var outerOffset = stack.Allocated;
|
||||
|
||||
using (stack.CreateScope(handle))
|
||||
{
|
||||
var pInner = stack.Allocate(128, 8, AllocationOption.None);
|
||||
Assert.IsNotNull(pInner);
|
||||
Assert.IsTrue(stack.Allocated > outerOffset);
|
||||
}
|
||||
|
||||
Assert.AreEqual(outerOffset, stack.Allocated);
|
||||
}
|
||||
|
||||
Assert.AreEqual(0u, (uint)stack.Allocated);
|
||||
}
|
||||
finally
|
||||
{
|
||||
stack.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void VirtualStack_MultipleAllocations_Works()
|
||||
{
|
||||
var stack = new VirtualStack(1024 * 1024);
|
||||
try
|
||||
{
|
||||
var handle = default(AllocationHandle);
|
||||
using (stack.CreateScope(handle))
|
||||
{
|
||||
var p1 = (byte*)stack.Allocate(32, 8, AllocationOption.Clear);
|
||||
Assert.IsNotNull(p1);
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
Assert.AreEqual(0, p1[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
p1[i] = 0x42;
|
||||
}
|
||||
|
||||
var same = (byte*)stack.Reallocate(p1, 32, 64, 8, AllocationOption.Clear);
|
||||
Assert.AreEqual((nint)p1, (nint)same);
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
Assert.AreEqual(0x42, same[i]);
|
||||
}
|
||||
|
||||
for (var i = 32; i < 64; i++)
|
||||
{
|
||||
Assert.AreEqual(0, same[i]);
|
||||
}
|
||||
|
||||
var p2 = (byte*)stack.Allocate(16, 8, AllocationOption.None);
|
||||
Assert.IsNotNull(p2);
|
||||
|
||||
for (var i = 0; i < 64; i++)
|
||||
{
|
||||
same[i] = (byte)(255 - i);
|
||||
}
|
||||
|
||||
var moved = (byte*)stack.Reallocate(same, 64, 96, 8, AllocationOption.None);
|
||||
Assert.IsNotNull(moved);
|
||||
for (var i = 0; i < 64; i++)
|
||||
{
|
||||
Assert.AreEqual((byte)(255 - i), moved[i]);
|
||||
}
|
||||
|
||||
stack.Reset();
|
||||
Assert.AreEqual(0u, (uint)stack.Allocated);
|
||||
var firstAfterReset = (byte*)stack.Allocate(8, 8, AllocationOption.None);
|
||||
Assert.AreEqual((nint)stack.Buffer, (nint)firstAfterReset);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
stack.Dispose();
|
||||
}
|
||||
|
||||
Assert.IsNull(stack.Buffer);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void VirtualStack_AllocationFailsOutsideScope()
|
||||
{
|
||||
var stack = new VirtualStack(128 * 1024);
|
||||
try
|
||||
{
|
||||
Assert.ThrowsExactly<InvalidOperationException>(() => stack.Allocate(32, 8, AllocationOption.Clear));
|
||||
|
||||
stack.Dispose();
|
||||
Assert.IsTrue(stack.Buffer == null);
|
||||
}
|
||||
finally
|
||||
{
|
||||
stack.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void VirtualStack_InvalidAlignment_Throws()
|
||||
{
|
||||
var stack = new VirtualStack(128 * 1024);
|
||||
try
|
||||
{
|
||||
using (stack.CreateScope(default))
|
||||
{
|
||||
Assert.ThrowsExactly<ArgumentException>(() => stack.Allocate(32, 3, AllocationOption.None));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
stack.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void VirtualStack_ZeroSizeAllocation_ReturnsNull()
|
||||
{
|
||||
var stack = new VirtualStack(128 * 1024);
|
||||
try
|
||||
{
|
||||
using (stack.CreateScope(default))
|
||||
{
|
||||
Assert.IsNull(stack.Allocate(0, 8, AllocationOption.None));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
stack.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void VirtualStack_AllocationExceedsReservedCapacity_ReturnsNull()
|
||||
{
|
||||
var stack = new VirtualStack(1024);
|
||||
try
|
||||
{
|
||||
using (stack.CreateScope(default))
|
||||
{
|
||||
Assert.IsNull(stack.Allocate(128 * 1024, 8, AllocationOption.None));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
stack.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void VirtualStack_ReallocateSmallerSize_ReturnsSamePointer()
|
||||
{
|
||||
var stack = new VirtualStack(128 * 1024);
|
||||
try
|
||||
{
|
||||
using (stack.CreateScope(default))
|
||||
{
|
||||
var p = (byte*)stack.Allocate(64, 8);
|
||||
Assert.IsNotNull(p);
|
||||
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
p[i] = (byte)(i + 1);
|
||||
}
|
||||
|
||||
var shrunk = (byte*)stack.Reallocate(p, 64, 32, 8, AllocationOption.None);
|
||||
Assert.AreEqual((nint)p, (nint)shrunk);
|
||||
for (var i = 0; i < 32; i++)
|
||||
{
|
||||
Assert.AreEqual((byte)(i + 1), shrunk[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
stack.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void VirtualStack_AllocationRequiresCommitment()
|
||||
{
|
||||
var stack = new VirtualStack(128 * 1024);
|
||||
try
|
||||
{
|
||||
var handle = default(AllocationHandle);
|
||||
using (stack.CreateScope(handle))
|
||||
{
|
||||
Assert.AreEqual(0u, (uint)stack.Committed);
|
||||
|
||||
var p = (byte*)stack.Allocate(64, 8);
|
||||
Assert.IsNotNull(p);
|
||||
|
||||
Assert.IsTrue(stack.Committed >= 64);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
stack.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Misaki.HighPerformance.Test/UnitTest/Utility.cs
Normal file
22
Misaki.HighPerformance.Test/UnitTest/Utility.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Misaki.HighPerformance.Test.UnitTest;
|
||||
|
||||
internal static class Utility
|
||||
{
|
||||
extension(Assert)
|
||||
{
|
||||
public unsafe static void IsNull(void* ptr, string message = "Expected pointer to be null.", [CallerArgumentExpression(nameof(ptr))] string conditionExpression = "")
|
||||
{
|
||||
Assert.IsTrue(ptr == null, message, conditionExpression);
|
||||
}
|
||||
|
||||
public unsafe static void IsNotNull(void* ptr, string message = "Expected pointer to be not null.", [CallerArgumentExpression(nameof(ptr))] string conditionExpression = "")
|
||||
{
|
||||
Assert.IsTrue(ptr != null, message, conditionExpression);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user