Refactor AllocationManager and enhance debug tracking
Refactored `AllocationManager` to introduce intrusive allocation tracking with `AllocationHeader` structs for debug mode. Added lightweight allocation counters for non-debug mode. Enhanced memory leak detection with detailed stack traces and `MemoryLeakException`. Simplified `AllocationInfo` by removing the `Allocator` property. Updated `AllocationOption` enum to remove `UnTracked` and clarified documentation. Improved unsafe collections (`UnsafeArray`, `UnsafeStack`, etc.) with strongly-typed enumerators and better compatibility with `IEnumerable<T>`. Enhanced `UnsafeStack` with a dedicated `Enumerator` struct and consistent constructor parameters. Refactored `MemoryLeakException` to support detailed allocation info and improved stack trace formatting. Simplified `MemoryUtility` by removing redundant null checks. Added unit tests for `AllocationManager`, `UnsafeArray`, and `UnsafeStack` to validate memory management and functionality. Updated `Program.cs` with new examples. Cleaned up namespaces, removed redundant `using` directives, and improved XML documentation. Applied `MethodImplOptions.AggressiveInlining` to performance-critical methods.
This commit is contained in:
@@ -1,6 +1,4 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Contracts;
|
using Misaki.HighPerformance.LowLevel.Contracts;
|
||||||
using Misaki.HighPerformance.LowLevel.Exceptions;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
@@ -20,14 +18,6 @@ public readonly unsafe struct AllocationInfo
|
|||||||
get; init;
|
get; init;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the allocator used for the allocation.
|
|
||||||
/// </summary>
|
|
||||||
public void* Allocator
|
|
||||||
{
|
|
||||||
get; init;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the stack trace at the time of allocation for debugging purposes.
|
/// Get the stack trace at the time of allocation for debugging purposes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -43,6 +33,17 @@ public readonly unsafe struct AllocationInfo
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static unsafe class AllocationManager
|
public static unsafe class AllocationManager
|
||||||
{
|
{
|
||||||
|
// === Intrusive allocation tracking (enabled when debug layer is on) ===
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct AllocationHeader
|
||||||
|
{
|
||||||
|
public AllocationHeader* prev;
|
||||||
|
public AllocationHeader* next;
|
||||||
|
public void* basePtr; // pointer returned by underlying allocator
|
||||||
|
public nuint userSize; // requested size from the user
|
||||||
|
public nint stackHandle; // GCHandle to managed StackTrace (stored as IntPtr)
|
||||||
|
}
|
||||||
|
|
||||||
private unsafe struct ArenaAllocator : IAllocator, IDisposable
|
private unsafe struct ArenaAllocator : IAllocator, IDisposable
|
||||||
{
|
{
|
||||||
private DynamicArena _arena;
|
private DynamicArena _arena;
|
||||||
@@ -111,49 +112,17 @@ public static unsafe class AllocationManager
|
|||||||
|
|
||||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
|
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
|
||||||
{
|
{
|
||||||
var ptr = AlignedAlloc(size, alignment);
|
return HeapAlloc(size, alignment, allocationOption);
|
||||||
|
|
||||||
if (allocationOption.HasFlag(AllocationOption.Clear))
|
|
||||||
{
|
|
||||||
MemClear(ptr, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!allocationOption.HasFlag(AllocationOption.UnTracked))
|
|
||||||
{
|
|
||||||
TrackAllocation(ptr, size, instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
|
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
|
||||||
{
|
{
|
||||||
var newPtr = AlignedRealloc(ptr, newSize, alignment);
|
return HeapRealloc(ptr, oldSize, newSize, alignment, allocationOption);
|
||||||
|
|
||||||
if (allocationOption.HasFlag(AllocationOption.Clear))
|
|
||||||
{
|
|
||||||
if (newSize > oldSize)
|
|
||||||
{
|
|
||||||
MemClear((byte*)newPtr + oldSize, newSize - oldSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!allocationOption.HasFlag(AllocationOption.UnTracked))
|
|
||||||
{
|
|
||||||
UpdateAllocation(ptr, newPtr, newSize, instance);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UntrackAllocation(ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return newPtr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void FreeBlock(void* instance, void* ptr)
|
private static void FreeBlock(void* instance, void* ptr)
|
||||||
{
|
{
|
||||||
AlignedFree(ptr);
|
HeapFree(ptr);
|
||||||
UntrackAllocation(ptr);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,32 +176,193 @@ public static unsafe class AllocationManager
|
|||||||
|
|
||||||
private const uint _DEFAULT_MEMORY_POOL_SIZE = 512 * 1024;
|
private const uint _DEFAULT_MEMORY_POOL_SIZE = 512 * 1024;
|
||||||
|
|
||||||
private static readonly ArenaAllocator* s_arenaAllocator;
|
private static readonly ArenaAllocator* s_pArenaAllocator;
|
||||||
private static readonly HeapAllocator* s_persistentAllocator;
|
private static readonly HeapAllocator* s_pHeapAllocator;
|
||||||
private static readonly StackAllocator* s_stackAllocator;
|
private static readonly StackAllocator* s_pStackAllocator;
|
||||||
|
|
||||||
private static bool s_debugLayer;
|
private static bool s_debugLayer;
|
||||||
private static ConcurrentDictionary<nint, AllocationInfo>? s_allocated;
|
private static bool s_disposed;
|
||||||
|
|
||||||
|
private static AllocationHeader* s_liveHead;
|
||||||
|
private static SpinLock s_liveLock;
|
||||||
|
|
||||||
|
// Lightweight allocation counter for non-debug layer (no sizes, just count of live heap blocks)
|
||||||
|
private static long s_activeHeapAllocations;
|
||||||
|
|
||||||
static AllocationManager()
|
static AllocationManager()
|
||||||
{
|
{
|
||||||
s_arenaAllocator = (ArenaAllocator*)NativeMemory.Alloc((nuint)sizeof(ArenaAllocator));
|
s_pArenaAllocator = (ArenaAllocator*)NativeMemory.Alloc((nuint)sizeof(ArenaAllocator));
|
||||||
s_persistentAllocator = (HeapAllocator*)NativeMemory.Alloc((nuint)sizeof(HeapAllocator));
|
s_pHeapAllocator = (HeapAllocator*)NativeMemory.Alloc((nuint)sizeof(HeapAllocator));
|
||||||
s_stackAllocator = (StackAllocator*)NativeMemory.Alloc((nuint)sizeof(StackAllocator));
|
s_pStackAllocator = (StackAllocator*)NativeMemory.Alloc((nuint)sizeof(StackAllocator));
|
||||||
|
|
||||||
s_arenaAllocator->Init(_DEFAULT_MEMORY_POOL_SIZE);
|
s_liveLock = new SpinLock(false);
|
||||||
s_persistentAllocator->Init();
|
|
||||||
s_stackAllocator->Init();
|
s_pArenaAllocator->Init(_DEFAULT_MEMORY_POOL_SIZE);
|
||||||
|
s_pHeapAllocator->Init();
|
||||||
|
s_pStackAllocator->Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static byte* AlignUp(byte* p, nuint alignment)
|
||||||
|
{
|
||||||
|
var a = alignment == 0 ? (nuint)IntPtr.Size : alignment;
|
||||||
|
return (byte*)(((nuint)p + (a - 1)) & ~(a - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static GCHandle HeaderGetHandle(AllocationHeader* header) => GCHandle.FromIntPtr(header->stackHandle);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void HeaderSetHandle(AllocationHeader* header, GCHandle handle) => header->stackHandle = GCHandle.ToIntPtr(handle);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void HeaderFreeHandle(AllocationHeader* header)
|
||||||
|
{
|
||||||
|
if (header->stackHandle != 0)
|
||||||
|
{
|
||||||
|
GCHandle.FromIntPtr(header->stackHandle).Free();
|
||||||
|
header->stackHandle = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void LinkHeader(AllocationHeader* header)
|
||||||
|
{
|
||||||
|
var taken = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
s_liveLock.Enter(ref taken);
|
||||||
|
header->prev = null;
|
||||||
|
header->next = s_liveHead;
|
||||||
|
if (s_liveHead != null)
|
||||||
|
{
|
||||||
|
s_liveHead->prev = header;
|
||||||
|
}
|
||||||
|
s_liveHead = header;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (taken)
|
||||||
|
{
|
||||||
|
s_liveLock.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void UnlinkHeader(AllocationHeader* header)
|
||||||
|
{
|
||||||
|
var taken = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
s_liveLock.Enter(ref taken);
|
||||||
|
var prev = header->prev;
|
||||||
|
var next = header->next;
|
||||||
|
|
||||||
|
if (prev != null)
|
||||||
|
prev->next = next;
|
||||||
|
else
|
||||||
|
s_liveHead = next;
|
||||||
|
|
||||||
|
if (next != null)
|
||||||
|
next->prev = prev;
|
||||||
|
|
||||||
|
header->prev = header->next = null;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (taken)
|
||||||
|
{
|
||||||
|
s_liveLock.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void* DebugAllocate(nuint size, nuint alignment)
|
||||||
|
{
|
||||||
|
// Over-allocate to fit header + alignment padding; we align the user pointer, header is placed just before it.
|
||||||
|
var pad = alignment == 0 ? (nuint)IntPtr.Size : alignment;
|
||||||
|
var total = size + (nuint)sizeof(AllocationHeader) + (pad - 1);
|
||||||
|
|
||||||
|
var basePtr = AlignedAlloc(total, pad);
|
||||||
|
var user = AlignUp((byte*)basePtr + (nuint)sizeof(AllocationHeader), pad);
|
||||||
|
var header = (AllocationHeader*)(user - (nuint)sizeof(AllocationHeader));
|
||||||
|
|
||||||
|
header->basePtr = basePtr;
|
||||||
|
header->userSize = size;
|
||||||
|
HeaderSetHandle(header, GCHandle.Alloc(new StackTrace(2, true)));
|
||||||
|
|
||||||
|
LinkHeader(header);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void DebugFree(void* userPtr)
|
||||||
|
{
|
||||||
|
var header = (AllocationHeader*)((byte*)userPtr - (nuint)sizeof(AllocationHeader));
|
||||||
|
UnlinkHeader(header);
|
||||||
|
HeaderFreeHandle(header);
|
||||||
|
AlignedFree(header->basePtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void* DebugReallocate(void* userPtr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
|
||||||
|
{
|
||||||
|
if (userPtr == null)
|
||||||
|
{
|
||||||
|
return DebugAllocate(newSize, alignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldHeader = (AllocationHeader*)((byte*)userPtr - (nuint)sizeof(AllocationHeader));
|
||||||
|
var handle = HeaderGetHandle(oldHeader); // preserve original allocation StackTrace
|
||||||
|
|
||||||
|
var pad = alignment == 0 ? (nuint)IntPtr.Size : alignment;
|
||||||
|
var total = newSize + (nuint)sizeof(AllocationHeader) + (pad - 1);
|
||||||
|
var newBase = AlignedAlloc(total, pad);
|
||||||
|
var newUser = AlignUp((byte*)newBase + (nuint)sizeof(AllocationHeader), pad);
|
||||||
|
var newHeader = (AllocationHeader*)(newUser - (nuint)sizeof(AllocationHeader));
|
||||||
|
|
||||||
|
newHeader->basePtr = newBase;
|
||||||
|
newHeader->userSize = newSize;
|
||||||
|
HeaderSetHandle(newHeader, handle); // transfer ownership to the new header
|
||||||
|
|
||||||
|
LinkHeader(newHeader);
|
||||||
|
|
||||||
|
// Mirror original behavior: copy newSize bytes
|
||||||
|
MemCpy(newUser, userPtr, newSize);
|
||||||
|
if (allocationOption.HasFlag(AllocationOption.Clear) && newSize > oldSize)
|
||||||
|
{
|
||||||
|
MemClear((byte*)newUser + oldSize, newSize - oldSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlink and free the old block (without freeing the StackTrace handle again)
|
||||||
|
oldHeader->stackHandle = 0;
|
||||||
|
UnlinkHeader(oldHeader);
|
||||||
|
AlignedFree(oldHeader->basePtr);
|
||||||
|
|
||||||
|
return newUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of live persistent heap allocations when the debug layer is disabled.
|
||||||
|
/// </summary>
|
||||||
|
public static long LiveHeapAllocationCount => Interlocked.Read(ref s_activeHeapAllocations);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables the debug layer, allowing additional diagnostic information to be collected.
|
/// Enables the debug layer, allowing additional diagnostic information to be collected.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void EnableDebugLayer()
|
public static void EnableDebugLayer()
|
||||||
{
|
{
|
||||||
|
// To avoid ambiguity between pointers allocated before/after enabling, this must be called
|
||||||
|
// before any heap allocations are live.
|
||||||
|
if (Interlocked.Read(ref s_activeHeapAllocations) != 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("EnableDebugLayer must be called before any heap allocations are active.");
|
||||||
|
}
|
||||||
|
|
||||||
s_debugLayer = true;
|
s_debugLayer = true;
|
||||||
s_allocated ??= new(-1, 64);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -247,80 +377,95 @@ public static unsafe class AllocationManager
|
|||||||
switch (allocator)
|
switch (allocator)
|
||||||
{
|
{
|
||||||
case Allocator.Temp:
|
case Allocator.Temp:
|
||||||
return ref s_arenaAllocator->Handle;
|
return ref s_pArenaAllocator->Handle;
|
||||||
case Allocator.Persistent:
|
case Allocator.Persistent:
|
||||||
return ref s_persistentAllocator->Handle;
|
return ref s_pHeapAllocator->Handle;
|
||||||
case Allocator.Stack:
|
case Allocator.Stack:
|
||||||
return ref s_stackAllocator->Handle;
|
return ref s_pStackAllocator->Handle;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentException("Target allocator type does not support custom allocation.", nameof(allocator));
|
throw new ArgumentException("Target allocator type does not support custom allocation.", nameof(allocator));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tracks a memory allocation in the allocation manager.
|
/// Allocates a block of memory from the heap with the specified size and alignment, using the given allocation
|
||||||
|
/// options.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ptr">The pointer to the allocated memory.</param>
|
/// <param name="size">The number of bytes to allocate. Must be greater than zero.</param>
|
||||||
/// <param name="allocationSize">The size of the allocation in bytes.</param>
|
/// <param name="alignment">The alignment, in bytes, for the allocated memory block. Must be a power of two.</param>
|
||||||
/// <param name="allocator">The allocator used for the allocation.</param>
|
/// <param name="allocationOption">An optional set of flags that control allocation behavior, such as whether the memory should be cleared or
|
||||||
/// <param name="freeFunc">The function pointer used to free the allocated memory.</param>
|
/// tracked. The default is <see cref="AllocationOption.None"/>.</param>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
/// <returns>A pointer to the beginning of the allocated memory block.</returns>
|
||||||
public static void TrackAllocation(void* ptr, nuint allocationSize, void* allocator)
|
/// <exception cref="OutOfMemoryException">Thrown if the allocation fails.</exception>
|
||||||
|
public static void* HeapAlloc(nuint size, nuint alignment, AllocationOption allocationOption = AllocationOption.None)
|
||||||
{
|
{
|
||||||
if (!s_debugLayer || s_allocated == null || ptr == null)
|
void* ptr;
|
||||||
|
if (s_debugLayer)
|
||||||
{
|
{
|
||||||
return;
|
ptr = DebugAllocate(size, alignment);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ptr = AlignedAlloc(size, alignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
s_allocated[(nint)ptr] = new AllocationInfo
|
if (allocationOption.HasFlag(AllocationOption.Clear))
|
||||||
{
|
{
|
||||||
Size = allocationSize,
|
MemClear(ptr, size);
|
||||||
Allocator = allocator,
|
}
|
||||||
StackTrace = new StackTrace(true)
|
|
||||||
};
|
Interlocked.Increment(ref s_activeHeapAllocations);
|
||||||
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the allocation tracking information by replacing the old pointer with a new pointer.
|
/// Reallocates a block of memory to a new size and alignment, optionally clearing newly allocated memory and
|
||||||
/// </summary>
|
/// applying allocation options.
|
||||||
/// <param name="oldPtr">A pointer to the previously allocated memory. If <paramref name="oldPtr"/> is not tracked, the method does nothing.</param>
|
/// </summary>\
|
||||||
/// <param name="newPtr">A pointer to the newly allocated memory. This pointer will replace <paramref name="oldPtr"/> in the allocation tracking.</param>
|
/// <param name="ptr">A pointer to the previously allocated memory block to be reallocated. Can be <see langword="null"/> to allocate new memory.</param>
|
||||||
/// <param name="allocationSize">The size, in bytes, of the new allocation.</param>
|
/// <param name="oldSize">The size, in bytes, of the memory block currently pointed to by <paramref name="ptr"/>.</param>
|
||||||
/// <param name="allocator">A pointer to the allocator responsible for the new allocation.</param>
|
/// <param name="newSize">The desired size, in bytes, for the reallocated memory block.</param>
|
||||||
/// <param name="freeFunc">A delegate or function pointer used to free the memory associated with the allocation.</param>
|
/// <param name="alignment">The required alignment, in bytes, for the reallocated memory block. Must be a power of two.</param>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
/// <param name="allocationOption">An optional set of flags that control allocation behavior, such as whether to clear newly allocated memory or
|
||||||
public static void UpdateAllocation(void* oldPtr, void* newPtr, nuint allocationSize, void* allocator)
|
/// track the allocation. The default is <see cref="AllocationOption.None"/>.</param>
|
||||||
|
/// <returns>A pointer to the reallocated memory block with the specified size and alignment. Returns <see langword="null"/>
|
||||||
|
/// if the allocation fails.</returns>
|
||||||
|
public static void* HeapRealloc(void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption = AllocationOption.None)
|
||||||
{
|
{
|
||||||
if (!s_debugLayer || s_allocated == null || oldPtr == null || newPtr == null)
|
if (s_debugLayer)
|
||||||
{
|
{
|
||||||
return;
|
return DebugReallocate(ptr, oldSize, newSize, alignment, allocationOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we don't find the allocation info, that means the oldPtr was not tracked
|
var newPtr = AlignedRealloc(ptr, newSize, alignment);
|
||||||
if (s_allocated.Remove((nint)oldPtr, out var info))
|
if (allocationOption.HasFlag(AllocationOption.Clear))
|
||||||
{
|
{
|
||||||
s_allocated[(nint)newPtr] = new AllocationInfo
|
if (newSize > oldSize)
|
||||||
{
|
{
|
||||||
Size = allocationSize,
|
MemClear((byte*)newPtr + oldSize, newSize - oldSize);
|
||||||
Allocator = allocator,
|
}
|
||||||
StackTrace = info.StackTrace
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return newPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes the specified memory allocation from the tracking system.
|
/// Releases a block of unmanaged memory previously allocated by the heap allocator.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ptr">A pointer to the memory allocation to untrack.</param>
|
/// <param name="ptr">A pointer to the memory block to be freed. The pointer must have been returned by a compatible heap allocation
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
/// method and must not be null.</param>
|
||||||
public static void UntrackAllocation(void* ptr)
|
public static void HeapFree(void* ptr)
|
||||||
{
|
{
|
||||||
if (s_allocated == null)
|
if (s_debugLayer)
|
||||||
{
|
{
|
||||||
return;
|
DebugFree(ptr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AlignedFree(ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
s_allocated.Remove((nint)ptr, out _);
|
Interlocked.Decrement(ref s_activeHeapAllocations);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -329,7 +474,7 @@ public static unsafe class AllocationManager
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void ResetTempAllocator()
|
public static void ResetTempAllocator()
|
||||||
{
|
{
|
||||||
s_arenaAllocator->Reset();
|
s_pArenaAllocator->Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -347,36 +492,74 @@ public static unsafe class AllocationManager
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static void Dispose()
|
public static void Dispose()
|
||||||
{
|
{
|
||||||
if (s_allocated != null)
|
if (s_disposed)
|
||||||
{
|
{
|
||||||
nuint unfreeBytes = 0u;
|
return;
|
||||||
foreach (var pair in s_allocated)
|
}
|
||||||
|
|
||||||
|
// In debug mode, walk the intrusive list to surface any leaks.
|
||||||
|
if (s_debugLayer)
|
||||||
|
{
|
||||||
|
var snapshot = new List<AllocationInfo>();
|
||||||
|
var taken = false;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
unfreeBytes += pair.Value.Size;
|
s_liveLock.Enter(ref taken);
|
||||||
|
if (s_liveHead != null)
|
||||||
|
{
|
||||||
|
snapshot.Capacity = 128;
|
||||||
|
for (var p = s_liveHead; p != null; p = p->next)
|
||||||
|
{
|
||||||
|
var trace = (StackTrace)HeaderGetHandle(p).Target!;
|
||||||
|
snapshot.Add(new AllocationInfo
|
||||||
|
{
|
||||||
|
Size = p->userSize,
|
||||||
|
StackTrace = trace
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (taken)
|
||||||
|
{
|
||||||
|
s_liveLock.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nuint unfreeBytes = 0u;
|
||||||
|
foreach (var info in snapshot)
|
||||||
|
{
|
||||||
|
unfreeBytes += info.Size;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unfreeBytes > 0u)
|
if (unfreeBytes > 0u)
|
||||||
{
|
{
|
||||||
throw new MemoryLeakException([.. s_allocated.Values]);
|
throw new MemoryLeakException(snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
s_allocated.Clear();
|
|
||||||
}
|
}
|
||||||
|
else if (s_activeHeapAllocations != 0)
|
||||||
if (s_arenaAllocator != null)
|
|
||||||
{
|
{
|
||||||
s_arenaAllocator->Dispose();
|
throw new MemoryLeakException($"Found {s_activeHeapAllocations} memory lakes! Please enable debug layer for more informations.");
|
||||||
NativeMemory.Free(s_arenaAllocator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s_stackAllocator != null)
|
if (s_pArenaAllocator != null)
|
||||||
{
|
{
|
||||||
NativeMemory.Free(s_stackAllocator);
|
s_pArenaAllocator->Dispose();
|
||||||
|
NativeMemory.Free(s_pArenaAllocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s_persistentAllocator != null)
|
if (s_pHeapAllocator != null)
|
||||||
{
|
{
|
||||||
NativeMemory.Free(s_persistentAllocator);
|
NativeMemory.Free(s_pHeapAllocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (s_pStackAllocator != null)
|
||||||
|
{
|
||||||
|
NativeMemory.Free(s_pStackAllocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
s_activeHeapAllocations = 0;
|
||||||
|
s_disposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,16 @@
|
|||||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
public enum AllocationOption : byte
|
public enum AllocationOption : byte
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Default allocation option. Values are uninitialized.
|
||||||
|
/// </summary>
|
||||||
None = 0,
|
None = 0,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allocator for initialized memory.
|
/// Clear the memory to zero upon allocation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Clear = 1 << 0,
|
Clear = 1 << 0,
|
||||||
/// <summary>
|
|
||||||
/// Allocator for untracked memory.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Use this option carefully, as the allocation manager will not track the memory.
|
|
||||||
/// No warning will be given if the memory is not freed.
|
|
||||||
/// </remarks>
|
|
||||||
UnTracked = 1 << 1,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Allocator : byte
|
public enum Allocator : byte
|
||||||
@@ -23,15 +18,15 @@ public enum Allocator : byte
|
|||||||
// Make the first allocator as invalid because we don't want to user create a default collection without passing any parameters
|
// Make the first allocator as invalid because we don't want to user create a default collection without passing any parameters
|
||||||
Invalid,
|
Invalid,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allocator for temporary allocations. Allocations are released after use automatically.
|
/// Allocator for temporary allocations. Allocations are automatically released after use automatically.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Temp,
|
Temp,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allocator for persistent allocations. Allocations are not released after use.
|
/// Allocator for persistent allocations. Allocations are not automatically released after use.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Persistent,
|
Persistent,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allocator for stack allocations. Must have at least one active stack scope. Allocations are released when the stack scope is exited.
|
/// Allocator for stack allocations. Must have at least one active stack scope. Allocations are automatically released when the stack scope is exited.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Stack
|
Stack
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ public unsafe struct Stack : IDisposable
|
|||||||
{
|
{
|
||||||
private const nuint _DEFAULT_SIZE = 1024 * 1024; // 1MB
|
private const nuint _DEFAULT_SIZE = 1024 * 1024; // 1MB
|
||||||
|
|
||||||
public readonly struct Scope : IDisposable
|
public readonly ref struct Scope : IDisposable
|
||||||
{
|
{
|
||||||
private readonly Stack* _allocator;
|
private readonly Stack* _allocator;
|
||||||
private readonly nuint _originalOffset;
|
private readonly nuint _originalOffset;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
||||||
using Misaki.HighPerformance.LowLevel.Contracts;
|
using Misaki.HighPerformance.LowLevel.Contracts;
|
||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
@@ -107,7 +107,8 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
|||||||
get => _buffer != null;
|
get => _buffer != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerator<T> GetEnumerator() => new Enumerator((UnsafeArray<T>*)UnsafeUtility.AddressOf(ref this));
|
public Enumerator GetEnumerator() => new ((UnsafeArray<T>*)UnsafeUtility.AddressOf(ref this));
|
||||||
|
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
||||||
using Misaki.HighPerformance.LowLevel.Contracts;
|
using Misaki.HighPerformance.LowLevel.Contracts;
|
||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
@@ -92,7 +92,8 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeCollection<KeyValuePai
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => new Enumerator((HashMapHelper<TKey>*)UnsafeUtility.AddressOf(ref _hashMap));
|
public Enumerator GetEnumerator() => new((HashMapHelper<TKey>*)UnsafeUtility.AddressOf(ref this));
|
||||||
|
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() => GetEnumerator();
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
||||||
using Misaki.HighPerformance.LowLevel.Contracts;
|
using Misaki.HighPerformance.LowLevel.Contracts;
|
||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
@@ -48,7 +48,8 @@ public unsafe struct UnsafeHashSet<T> : IUnsafeCollection<T>, IEnumerable<T>
|
|||||||
public readonly int Capacity => _hashMap.Capacity;
|
public readonly int Capacity => _hashMap.Capacity;
|
||||||
public readonly bool IsCreated => _hashMap.IsCreated;
|
public readonly bool IsCreated => _hashMap.IsCreated;
|
||||||
|
|
||||||
public IEnumerator<T> GetEnumerator() => new Enumerator((HashMapHelper<T>*)UnsafeUtility.AddressOf(ref _hashMap));
|
public Enumerator GetEnumerator() => new((HashMapHelper<T>*)UnsafeUtility.AddressOf(ref this));
|
||||||
|
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
||||||
using Misaki.HighPerformance.LowLevel.Contracts;
|
using Misaki.HighPerformance.LowLevel.Contracts;
|
||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
@@ -136,7 +136,8 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
|
|||||||
get => ref _array[index];
|
get => ref _array[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerator<T> GetEnumerator() => new Enumerator((UnsafeList<T>*)UnsafeUtility.AddressOf(ref this));
|
public Enumerator GetEnumerator() => new ((UnsafeList<T>*)UnsafeUtility.AddressOf(ref this));
|
||||||
|
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -80,7 +80,8 @@ public unsafe struct UnsafeQueue<T> : IUnsafeCollection<T>
|
|||||||
set => _array[index] = value;
|
set => _array[index] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerator<T> GetEnumerator() => new Enumerator((UnsafeQueue<T>*)UnsafeUtility.AddressOf(ref this));
|
public Enumerator GetEnumerator() => new((UnsafeQueue<T>*)UnsafeUtility.AddressOf(ref this));
|
||||||
|
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -67,8 +67,8 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
|
|||||||
|
|
||||||
public readonly bool IsCreated => _data.IsCreated && _freeSlots.IsCreated;
|
public readonly bool IsCreated => _data.IsCreated && _freeSlots.IsCreated;
|
||||||
|
|
||||||
public IEnumerator<T> GetEnumerator() => new Enumerator((UnsafeSlotMap<T>*)UnsafeUtility.AddressOf(ref this));
|
public Enumerator GetEnumerator() => new((UnsafeSlotMap<T>*)UnsafeUtility.AddressOf(ref this));
|
||||||
|
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -80,7 +80,8 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
public readonly int Capacity => _capacity;
|
public readonly int Capacity => _capacity;
|
||||||
public readonly bool IsCreated => _dense.IsCreated && _sparse.IsCreated && _reverse.IsCreated;
|
public readonly bool IsCreated => _dense.IsCreated && _sparse.IsCreated && _reverse.IsCreated;
|
||||||
|
|
||||||
public IEnumerator<T> GetEnumerator() => new Enumerator((UnsafeSparseSet<T>*)UnsafeUtility.AddressOf(ref this));
|
public Enumerator GetEnumerator() => new((UnsafeSparseSet<T>*)UnsafeUtility.AddressOf(ref this));
|
||||||
|
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
||||||
using Misaki.HighPerformance.LowLevel.Contracts;
|
using Misaki.HighPerformance.LowLevel.Contracts;
|
||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
@@ -15,20 +15,64 @@ namespace Misaki.HighPerformance.LowLevel.Collections;
|
|||||||
public unsafe struct UnsafeStack<T> : IUnsafeCollection<T>
|
public unsafe struct UnsafeStack<T> : IUnsafeCollection<T>
|
||||||
where T : unmanaged
|
where T : unmanaged
|
||||||
{
|
{
|
||||||
|
public struct Enumerator : IEnumerator<T>
|
||||||
|
{
|
||||||
|
private readonly UnsafeStack<T>* _collection;
|
||||||
|
private int _index;
|
||||||
|
private T _value;
|
||||||
|
|
||||||
|
public Enumerator(UnsafeStack<T>* collection)
|
||||||
|
{
|
||||||
|
_collection = collection;
|
||||||
|
_index = collection->Count;
|
||||||
|
_value = default;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
_index--;
|
||||||
|
if (_index >= 0)
|
||||||
|
{
|
||||||
|
_value = UnsafeUtility.ReadArrayElement<T>(_collection->_array.GetUnsafePtr(), _index);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_index = _collection->Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly T Current
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _value;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly object IEnumerator.Current
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => Current;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private UnsafeArray<T> _array;
|
private UnsafeArray<T> _array;
|
||||||
private int _count;
|
private int _count;
|
||||||
|
|
||||||
public readonly int Count => _count;
|
public readonly int Count => _count;
|
||||||
public readonly bool IsCreated => _array.IsCreated;
|
public readonly bool IsCreated => _array.IsCreated;
|
||||||
|
|
||||||
public IEnumerator<T> GetEnumerator()
|
public Enumerator GetEnumerator() => new((UnsafeStack<T>*)UnsafeUtility.AddressOf(ref this));
|
||||||
{
|
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
|
||||||
throw new NotImplementedException();
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
}
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
{
|
|
||||||
return GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invalid constructor, use <see cref="UnsafeStack(int, Allocator, AllocationOption)"/> or <see cref="UnsafeStack(int, ref AllocationHandle, AllocationOption)"/> instead.
|
/// Invalid constructor, use <see cref="UnsafeStack(int, Allocator, AllocationOption)"/> or <see cref="UnsafeStack(int, ref AllocationHandle, AllocationOption)"/> instead.
|
||||||
@@ -41,23 +85,23 @@ public unsafe struct UnsafeStack<T> : IUnsafeCollection<T>
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the UnsafeStack class with the specified initial capacity and allocation options.
|
/// Initializes a new instance of the UnsafeStack class with the specified initial capacity and allocation options.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="initialCapacity">The number of elements the stack can initially hold. Must be greater than zero.</param>
|
/// <param name="capacity">The number of elements the stack can initially hold. Must be greater than zero.</param>
|
||||||
/// <param name="handle">A reference to an AllocationHandle used to manage the underlying memory allocation for the stack.</param>
|
/// <param name="handle">A reference to an AllocationHandle used to manage the underlying memory allocation for the stack.</param>
|
||||||
/// <param name="allocationOption">Specifies additional options for memory allocation. The default is AllocationOption.None.</param>
|
/// <param name="allocationOption">Specifies additional options for memory allocation. The default is AllocationOption.None.</param>
|
||||||
public UnsafeStack(int initialCapacity, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
|
public UnsafeStack(int capacity, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
|
||||||
{
|
{
|
||||||
_array = new UnsafeArray<T>(initialCapacity, ref handle, allocationOption);
|
_array = new UnsafeArray<T>(capacity, ref handle, allocationOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the UnsafeStack class with the specified initial capacity, allocator, and
|
/// Initializes a new instance of the UnsafeStack class with the specified initial capacity, allocator, and
|
||||||
/// allocation options.
|
/// allocation options.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="initialCapacity">The initial number of elements that the stack can hold. Must be greater than zero.</param>
|
/// <param name="capacity">The initial number of elements that the stack can hold. Must be greater than zero.</param>
|
||||||
/// <param name="allocator">The allocator to use for memory management of the stack's storage.</param>
|
/// <param name="allocator">The allocator to use for memory management of the stack's storage.</param>
|
||||||
/// <param name="allocationOption">The allocation option that determines how memory is allocated for the stack. The default is AllocationOption.None.</param>
|
/// <param name="allocationOption">The allocation option that determines how memory is allocated for the stack. The default is AllocationOption.None.</param>
|
||||||
public UnsafeStack(int initialCapacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
|
public UnsafeStack(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
|
||||||
: this(initialCapacity, ref AllocationManager.GetAllocationHandle(allocator), allocationOption)
|
: this(capacity, ref AllocationManager.GetAllocationHandle(allocator), allocationOption)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,28 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Misaki.HighPerformance.LowLevel.Exceptions;
|
namespace Misaki.HighPerformance.LowLevel;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An exception that is thrown when a memory leak is detected.
|
/// An exception that is thrown when a memory leak is detected.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="Infos">An array of AllocationInfo containing details about the memory leaks.</param>
|
/// <param name="Infos">An array of AllocationInfo containing details about the memory leaks.</param>
|
||||||
public class MemoryLeakException(params AllocationInfo[] Infos) : Exception
|
public class MemoryLeakException : Exception
|
||||||
{
|
{
|
||||||
|
private readonly IEnumerable<AllocationInfo>? _infos;
|
||||||
|
private readonly string _message = string.Empty;
|
||||||
|
|
||||||
|
public MemoryLeakException(IEnumerable<AllocationInfo> infos)
|
||||||
|
{
|
||||||
|
_infos = infos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MemoryLeakException(string message)
|
||||||
|
{
|
||||||
|
_message = message;
|
||||||
|
}
|
||||||
|
|
||||||
private static string GetMessage(StackTrace? stackTrace)
|
private static string GetMessage(StackTrace? stackTrace)
|
||||||
{
|
{
|
||||||
if (stackTrace == null)
|
if (stackTrace == null)
|
||||||
@@ -36,9 +49,15 @@ public class MemoryLeakException(params AllocationInfo[] Infos) : Exception
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
if (_infos == null)
|
||||||
|
{
|
||||||
|
return _message;
|
||||||
|
}
|
||||||
|
|
||||||
var stringBuilder = new StringBuilder();
|
var stringBuilder = new StringBuilder();
|
||||||
stringBuilder.AppendLine($"Found {Infos.Length} memory lakes!");
|
stringBuilder.AppendLine($"Found {_infos.Count()} memory lakes!");
|
||||||
foreach (var info in Infos)
|
|
||||||
|
foreach (var info in _infos)
|
||||||
{
|
{
|
||||||
stringBuilder.AppendLine(GetMessage(info.StackTrace));
|
stringBuilder.AppendLine(GetMessage(info.StackTrace));
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Misaki.HighPerformance.LowLevel.Utilities;
|
namespace Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
@@ -69,11 +69,6 @@ public static unsafe partial class MemoryUtility
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void Free(void* ptr)
|
public static void Free(void* ptr)
|
||||||
{
|
{
|
||||||
if (ptr == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeMemory.Free(ptr);
|
NativeMemory.Free(ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,11 +80,6 @@ public static unsafe partial class MemoryUtility
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void AlignedFree(void* ptr)
|
public static void AlignedFree(void* ptr)
|
||||||
{
|
{
|
||||||
if (ptr == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeMemory.AlignedFree(ptr);
|
NativeMemory.AlignedFree(ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,4 +43,14 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
|
||||||
var array = new UnsafeArray<int>(10, Allocator.Persistent);
|
//AllocationManager.EnableDebugLayer();
|
||||||
|
//var array = new UnsafeArray<int>(10, Allocator.Persistent);
|
||||||
|
//var array2 = new UnsafeArray<int>(10, Allocator.Persistent);
|
||||||
|
//array.Dispose();
|
||||||
|
//array2.Dispose();
|
||||||
|
//AllocationManager.Dispose();
|
||||||
|
|
||||||
|
using (AllocationManager.CreateStackScope())
|
||||||
|
{
|
||||||
|
var arr = new UnsafeArray<int>(10, Allocator.Stack);
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
using Misaki.HighPerformance.LowLevel;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
|
||||||
|
namespace Misaki.HighPerformance.Test.UnitTest.Buffer;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class TestAllocationManager
|
||||||
|
{
|
||||||
|
[TestInitialize]
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
AllocationManager.EnableDebugLayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ShouldNotLeakTest()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var array = new UnsafeArray<int>(10, Allocator.Persistent);
|
||||||
|
var array2 = new UnsafeArray<int>(10, Allocator.Persistent);
|
||||||
|
|
||||||
|
array.Dispose();
|
||||||
|
array2.Dispose();
|
||||||
|
|
||||||
|
AllocationManager.Dispose();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
var leaks = AllocationManager.LiveHeapAllocationCount;
|
||||||
|
Assert.AreEqual(0, leaks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ShouldLeakTest()
|
||||||
|
{
|
||||||
|
var array = new UnsafeArray<int>(10, Allocator.Persistent);
|
||||||
|
var array2 = new UnsafeArray<int>(10, Allocator.Persistent);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AllocationManager.Dispose();
|
||||||
|
}
|
||||||
|
catch (MemoryLeakException)
|
||||||
|
{
|
||||||
|
var leaks = AllocationManager.LiveHeapAllocationCount;
|
||||||
|
Assert.AreEqual(2, leaks);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
array.Dispose();
|
||||||
|
array2.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Fail("Expected MemoryLeakException was not thrown.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
|
||||||
|
namespace Misaki.HighPerformance.Test.UnitTest.Collections;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class TestUnsafeArray
|
||||||
|
{
|
||||||
|
private UnsafeArray<int> _arr;
|
||||||
|
|
||||||
|
[TestInitialize]
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
_arr = new UnsafeArray<int>(16, Allocator.Persistent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCleanup]
|
||||||
|
public void Cleanup()
|
||||||
|
{
|
||||||
|
_arr.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestIndexAccess()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _arr.Count; i++)
|
||||||
|
{
|
||||||
|
_arr[i] = i * 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < _arr.Count; i++)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(i * 10, _arr[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestEnumeration()
|
||||||
|
{
|
||||||
|
_arr.Clear();
|
||||||
|
|
||||||
|
int expectedValue = 0;
|
||||||
|
foreach (var item in _arr)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(expectedValue, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestIsCreated()
|
||||||
|
{
|
||||||
|
Assert.IsTrue(_arr.IsCreated);
|
||||||
|
_arr.Dispose();
|
||||||
|
Assert.IsFalse(_arr.IsCreated);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Misaki.HighPerformance.Test.UnitTest.Collections;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class TestUnsafeStack
|
||||||
|
{
|
||||||
|
private UnsafeStack<int> _stack;
|
||||||
|
|
||||||
|
[TestInitialize]
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
_stack = new UnsafeStack<int>(16, Allocator.Persistent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCleanup]
|
||||||
|
public void Cleanup()
|
||||||
|
{
|
||||||
|
_stack.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestPushPop()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
_stack.Push(i);
|
||||||
|
}
|
||||||
|
Assert.AreEqual(10, _stack.Count);
|
||||||
|
for (int i = 9; i >= 0; i--)
|
||||||
|
{
|
||||||
|
int value = _stack.Pop();
|
||||||
|
Assert.AreEqual(i, value);
|
||||||
|
}
|
||||||
|
Assert.AreEqual(0, _stack.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestPeek()
|
||||||
|
{
|
||||||
|
_stack.Push(42);
|
||||||
|
int value = _stack.Peek();
|
||||||
|
Assert.AreEqual(42, value);
|
||||||
|
Assert.AreEqual(1, _stack.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestEnumeration()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
_stack.Push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
int expected = 4;
|
||||||
|
foreach (var item in _stack)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(expected, item);
|
||||||
|
expected--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user