feat(memory): refactor allocation and add new queue

Refactored memory management by removing safety checks and introducing `MemoryHandle` for centralized tracking. Simplified allocation logic across allocators and enhanced `Dispose` methods for better resource cleanup.

Added `UnsafeChunkedQueue<T>`, a lock-free, dynamically resizing queue with chunk-based memory management, supporting parallel producers and consumers.

Updated unit tests to validate new queue functionality and ensure compatibility with refactored memory logic. Incremented assembly version to 1.6.12.

BREAKING CHANGE: Removed `#if MHP_ENABLE_SAFETY_CHECKS` blocks, altering memory validation behavior.
This commit is contained in:
2026-04-10 14:44:48 +09:00
parent dea8de60d0
commit a0deadc363
25 changed files with 647 additions and 456 deletions

View File

@@ -94,27 +94,15 @@ public static unsafe class AllocationManager
State = null,
Alloc = &Allocate,
Realloc = &Reallocate,
Free = &Free,
#if MHP_ENABLE_SAFETY_CHECKS
IsValid = &IsValid
#else
IsValid = null
#endif
Free = &Free
};
}
private static void* Allocate(void* _, nuint size, nuint alignment, AllocationOption allocationOption
#if MHP_ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle
#endif
)
private static void* Allocate(void* _, nuint size, nuint alignment, AllocationOption allocationOption)
{
var ptr = AlignedAlloc(size, alignment);
if (ptr == null)
{
#if MHP_ENABLE_SAFETY_CHECKS
*pHandle = MemoryHandle.Invalid;
#endif
return null;
}
@@ -123,38 +111,12 @@ public static unsafe class AllocationManager
MemClear(ptr, size);
}
#if MHP_ENABLE_SAFETY_CHECKS
*pHandle = AddAllocation(ptr, size);
#endif
return ptr;
}
private static void* Reallocate(void* _, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption
#if MHP_ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle
#endif
)
private static void* Reallocate(void* _, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
{
var newPtr = AlignedRealloc(ptr, newSize, alignment);
#if MHP_ENABLE_SAFETY_CHECKS
if (ptr == null && newPtr != null)
{
AddAllocation(newPtr, newSize);
}
else
{
if (newPtr == null)
{
RemoveAllocation(*pHandle);
}
else
{
UpdateAllocation(*pHandle, newPtr, newSize);
}
}
#endif
if (newPtr == null)
{
return null;
@@ -170,16 +132,9 @@ public static unsafe class AllocationManager
return newPtr;
}
private static void Free(void* _, void* ptr
#if MHP_ENABLE_SAFETY_CHECKS
, MemoryHandle handle
#endif
)
private static void Free(void* _, void* ptr)
{
AlignedFree(ptr);
#if MHP_ENABLE_SAFETY_CHECKS
RemoveAllocation(handle);
#endif
}
#if MHP_ENABLE_SAFETY_CHECKS
@@ -332,7 +287,7 @@ public static unsafe class AllocationManager
public static VirtualStack.Scope CreateStackScope()
{
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
EnsureThreadLocalStackInitialize();
return t_stackAllocator.Allocator.CreateScope(t_stackAllocator.AllocationHandle);
}
@@ -373,15 +328,23 @@ public static unsafe class AllocationManager
#if MHP_ENABLE_SAFETY_CHECKS
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
if (s_allocations.TryGetElement(handle.ID, handle.Generation, out var oldInfo))
if (newPtr == null)
{
s_allocations.Remove(handle.ID, handle.Generation);
}
else
{
var newInfo = oldInfo with
{
Address = (IntPtr)newPtr,
Size = newSize
};
s_allocations.UpdateElement(handle.ID, handle.Generation, newInfo);
if (s_allocations.TryGetElement(handle.ID, handle.Generation, out var oldInfo))
{
var newInfo = oldInfo with
{
Address = (IntPtr)newPtr,
Size = newSize
};
s_allocations.UpdateElement(handle.ID, handle.Generation, newInfo);
}
}
#endif
}
@@ -501,7 +464,6 @@ public static unsafe class AllocationManager
if (pStack != null)
{
pStack->Dispose();
Free(pStack);
}
}

View File

@@ -159,10 +159,12 @@ public unsafe struct Arena : IMemoryAllocator<Arena, Arena.CreationOptions>
return;
}
MemoryUtility.Free(_buffer);
var ptr = _buffer;
_buffer = null;
_size = 0;
_offset = 0;
MemoryUtility.Free(ptr);
}
}

View File

@@ -183,7 +183,12 @@ public unsafe struct DynamicArena : IMemoryAllocator<DynamicArena, DynamicArena.
return;
}
var current = _root;
var ptr = _root;
_root = null;
_current = null;
var current = ptr;
while (current != null)
{
var next = current->next;
@@ -191,8 +196,5 @@ public unsafe struct DynamicArena : IMemoryAllocator<DynamicArena, DynamicArena.
MemoryUtility.Free(current);
current = next;
}
_root = null;
_current = null;
}
}

View File

@@ -717,16 +717,6 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpti
}
}
var chunk = _chunks;
while (chunk != null)
{
var next = chunk->next;
AlignedFree(chunk->memory);
chunk = next;
}
_chunkArena.Dispose();
if (_caches != null)
{
MemoryUtility.Free(_caches);
@@ -739,7 +729,17 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpti
_instanceId = null;
}
var arena = _chunkArena;
var chunk = _chunks;
_chunks = null;
_cacheCount = 0;
while (chunk != null)
{
var next = chunk->next;
AlignedFree(chunk->memory);
chunk = next;
}
arena.Dispose();
}
}

View File

@@ -2,7 +2,7 @@ using System.Diagnostics.CodeAnalysis;
namespace Misaki.HighPerformance.LowLevel.Buffer;
public readonly struct MemoryHandle : IEquatable<MemoryHandle>
public readonly struct MemoryHandle : IDisposable, IEquatable<MemoryHandle>
{
public readonly int ID
{
@@ -16,12 +16,25 @@ public readonly struct MemoryHandle : IEquatable<MemoryHandle>
public static readonly MemoryHandle Invalid = default;
public bool IsValid => AllocationManager.ContainsAllocation(this);
public bool IsInvalid => !IsValid;
public MemoryHandle(int id, int generation)
{
ID = id + 1;
Generation = generation + 1;
}
public unsafe static MemoryHandle Create(void* address, nuint size)
{
return AllocationManager.AddAllocation(address, size);
}
public unsafe void Update(void* newAddress, nuint newSize)
{
AllocationManager.UpdateAllocation(this, newAddress, newSize);
}
public bool Equals(MemoryHandle other)
{
return ID == other.ID && Generation == other.Generation;
@@ -42,6 +55,11 @@ public readonly struct MemoryHandle : IEquatable<MemoryHandle>
return $"MemoryHandle(Id: {ID}, Generation: {Generation})";
}
public void Dispose()
{
AllocationManager.RemoveAllocation(this);
}
public static bool operator ==(MemoryHandle left, MemoryHandle right)
{
return left.Equals(right);
@@ -61,7 +79,7 @@ public readonly unsafe struct AllocationHandle
/// <summary>
/// Gets a pointer to the state instance associated with this allocation handle.
/// </summary>
public void* State
public required void* State
{
get; init;
}
@@ -69,7 +87,7 @@ public readonly unsafe struct AllocationHandle
/// <summary>
/// Gets a function pointer for allocating memory.
/// </summary>
public AllocFunc Alloc
public required AllocFunc Alloc
{
get; init;
}
@@ -77,7 +95,7 @@ public readonly unsafe struct AllocationHandle
/// <summary>
/// Gets a function pointer for reallocating memory.
/// </summary>
public ReallocFunc Realloc
public required ReallocFunc Realloc
{
get; init;
}
@@ -85,15 +103,7 @@ public readonly unsafe struct AllocationHandle
/// <summary>
/// Gets a function pointer for freeing allocated memory.
/// </summary>
public FreeFunc Free
{
get; init;
}
/// <summary>
/// Gets a function pointer for validating a memory handle.
/// </summary>
public IsValidFunc IsValid
public required FreeFunc Free
{
get; init;
}

View File

@@ -10,8 +10,10 @@ public unsafe struct MemoryBlock : IDisposable
private nuint _size;
private nuint _alignment;
private MemoryHandle _memoryHandle;
private AllocationHandle _allocationHandle;
#if MHP_ENABLE_SAFETY_CHECKS
private readonly MemoryHandle _memoryHandle;
#endif
private readonly AllocationHandle _allocationHandle;
public readonly nuint Size => _size;
public readonly nuint Alignment => _alignment;
@@ -23,14 +25,7 @@ public unsafe struct MemoryBlock : IDisposable
#if MHP_ENABLE_SAFETY_CHECKS
if (_buffer != null)
{
if (_allocationHandle.IsValid != null)
{
return _allocationHandle.IsValid(_allocationHandle.State, _memoryHandle);
}
else
{
return true;
}
return _memoryHandle.IsValid;
}
return false;
@@ -54,19 +49,12 @@ public unsafe struct MemoryBlock : IDisposable
throw new InvalidOperationException("Target allocation handle does not support allocation.");
}
#if MHP_ENABLE_SAFETY_CHECKS
MemoryHandle memHandle;
#endif
_buffer = handle.Alloc(handle.State, size, alignment, allocationOption
#if MHP_ENABLE_SAFETY_CHECKS
, &memHandle
#endif
);
_buffer = handle.Alloc(handle.State, size, alignment, allocationOption);
_size = size;
_alignment = alignment;
#if MHP_ENABLE_SAFETY_CHECKS
_memoryHandle = memHandle;
_memoryHandle = MemoryHandle.Create(_buffer, _size);
#endif
_allocationHandle = handle;
}
@@ -121,17 +109,10 @@ public unsafe struct MemoryBlock : IDisposable
return;
}
#if MHP_ENABLE_SAFETY_CHECKS
var memHandle = _memoryHandle;
#endif
_buffer = _allocationHandle.Realloc(_allocationHandle.State, _buffer, _size, newSize, _alignment, option
#if MHP_ENABLE_SAFETY_CHECKS
, &memHandle
#endif
);
_buffer = _allocationHandle.Realloc(_allocationHandle.State, _buffer, _size, newSize, _alignment, option);
_size = newSize;
#if MHP_ENABLE_SAFETY_CHECKS
_memoryHandle = memHandle;
_memoryHandle.Update(_buffer, _size);
#endif
}
@@ -277,44 +258,19 @@ public unsafe struct MemoryBlock : IDisposable
{
if (!IsCreated)
{
#if DEBUG
if (_buffer == null)
{
return;
}
var message = "The UnTypedArray is not created or already disposed.";
#if MHP_ENABLE_STACKTRACE
var stackTrace = new StackTrace(1, true);
var sb = new System.Text.StringBuilder();
foreach (var frame in stackTrace.GetFrames())
{
var fileName = frame?.GetFileName();
if (frame != null)
{
var methodInfo = DiagnosticMethodInfo.Create(frame);
sb.AppendLine($"File: {fileName}, Type: {methodInfo?.DeclaringTypeName}, Method: {methodInfo?.Name}, Line: {frame.GetFileLineNumber()}");
}
}
message += Environment.NewLine + sb.ToString();
#endif
Debug.WriteLine(message);
#endif
UnsafeCollectionUtility.ReportDoubleFree<MemoryBlock>(_buffer);
return;
}
if (_allocationHandle.Free != null)
{
_allocationHandle.Free(_allocationHandle.State, _buffer
#if MHP_ENABLE_SAFETY_CHECKS
, _memoryHandle
#endif
);
_allocationHandle.Free(_allocationHandle.State, _buffer);
}
#if MHP_ENABLE_SAFETY_CHECKS
_memoryHandle.Dispose();
#endif
_buffer = null;
_size = 0;
_alignment = 0;
}
}

View File

@@ -1,4 +1,3 @@
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Buffer;
@@ -14,72 +13,33 @@ public unsafe struct MemoryPool<TAllocator, TOpts> : IDisposable
public MemoryPool(in TOpts opts)
{
_pAllocator = (TAllocator*)Malloc((nuint)sizeof(TAllocator));
*_pAllocator = TAllocator.Create(opts);
var allocator = TAllocator.Create(opts);
_pAllocator = (TAllocator*)allocator.Allocate((nuint)sizeof(TAllocator), AlignOf<TAllocator>(), AllocationOption.None);
*_pAllocator = allocator;
_allocationHandle = new AllocationHandle
{
State = _pAllocator,
Alloc = &Allocate,
Realloc = &Reallocate,
Free = &Free,
IsValid = null
Free = &Free
};
}
private static void* Allocate(void* pAllocator, nuint size, nuint alignment, AllocationOption allocationOption
#if MHP_ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle
#endif
)
private static void* Allocate(void* pAllocator, nuint size, nuint alignment, AllocationOption allocationOption)
{
var ptr = ((TAllocator*)pAllocator)->Allocate(size, alignment, allocationOption);
#if MHP_ENABLE_SAFETY_CHECKS
if (ptr != null)
{
*pHandle = AllocationManager.AddAllocation(ptr, size);
}
#endif
return ptr;
return ((TAllocator*)pAllocator)->Allocate(size, alignment, allocationOption);
}
private static void* Reallocate(void* pAllocator, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption
#if MHP_ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle
#endif
)
private static void* Reallocate(void* pAllocator, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
{
var newPtr = ((TAllocator*)pAllocator)->Reallocate(ptr, oldSize, newSize, alignment, allocationOption);
#if MHP_ENABLE_SAFETY_CHECKS
if (ptr == null && newPtr != null)
{
*pHandle = AllocationManager.AddAllocation(newPtr, newSize);
}
else
{
if (newPtr == null)
{
AllocationManager.RemoveAllocation(*pHandle);
}
else
{
AllocationManager.UpdateAllocation(*pHandle, newPtr, newSize);
}
}
#endif
return newPtr;
return ((TAllocator*)pAllocator)->Reallocate(ptr, oldSize, newSize, alignment, allocationOption);
}
private static void Free(void* pAllocator, void* ptr
#if MHP_ENABLE_SAFETY_CHECKS
, MemoryHandle handle
#endif
)
private static void Free(void* pAllocator, void* ptr)
{
((TAllocator*)pAllocator)->Free(ptr);
#if MHP_ENABLE_SAFETY_CHECKS
AllocationManager.RemoveAllocation(handle);
#endif
}
public void Dispose()
@@ -91,8 +51,6 @@ public unsafe struct MemoryPool<TAllocator, TOpts> : IDisposable
_pAllocator->Dispose();
MemoryUtility.Free(_pAllocator);
_pAllocator = null;
_allocationHandle = default;
}

View File

@@ -33,9 +33,6 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOptio
_allocator = allocator;
_handle = handle;
_originalOffset = allocator->_offset;
#if MHP_ENABLE_SAFETY_CHECKS
_allocator->_activeScopeCount++;
#endif
}
public void Dispose()
@@ -43,9 +40,6 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOptio
if (_allocator != null)
{
_allocator->_offset = _allocator->_offset > _originalOffset ? _originalOffset : _allocator->_offset;
#if MHP_ENABLE_SAFETY_CHECKS
_allocator->_activeScopeCount--;
#endif
}
}
}
@@ -53,9 +47,6 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOptio
private byte* _buffer;
private nuint _size;
private nuint _offset;
#if MHP_ENABLE_SAFETY_CHECKS
private uint _activeScopeCount;
#endif
public readonly byte* Buffer => _buffer;
public readonly nuint Size => _size;
@@ -72,9 +63,6 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOptio
_buffer = (byte*)Malloc(size);
_size = size;
_offset = 0;
#if MHP_ENABLE_SAFETY_CHECKS
_activeScopeCount = 0;
#endif
}
/// <summary>
@@ -101,13 +89,6 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOptio
/// there is insufficient space in the buffer.</returns>
public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption = AllocationOption.None)
{
#if MHP_ENABLE_SAFETY_CHECKS
if (_activeScopeCount == 0)
{
throw new InvalidOperationException("Allocations can only be made within an active memory scope.");
}
#endif
if (size == 0)
{
return null;
@@ -197,10 +178,12 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOptio
return;
}
MemoryUtility.Free(_buffer);
var ptr = _buffer;
_buffer = null;
_size = 0;
_offset = 0;
_size = 0;
MemoryUtility.Free(ptr);
}
}

View File

@@ -474,6 +474,7 @@ public unsafe struct TLSF : IMemoryAllocator<TLSF, TLSF.CreationOptions>
MemoryUtility.Free(_blocks);
_blocks = null;
}
if (_slBitmaps != null)
{
MemoryUtility.Free(_slBitmaps);
@@ -481,14 +482,13 @@ public unsafe struct TLSF : IMemoryAllocator<TLSF, TLSF.CreationOptions>
}
MemoryChunk* chunk = _chunks;
_chunks = null;
while (chunk != null)
{
MemoryChunk* next = chunk->next;
AlignedFree(chunk->memory);
chunk = next;
}
_chunks = null;
_flBitmap = 0;
}
}

View File

@@ -232,14 +232,18 @@ public unsafe struct VirtualArena : IMemoryAllocator<VirtualArena, VirtualArena.
public void Dispose()
{
if (_baseAddress != null)
if (_baseAddress == null)
{
Munmap(_baseAddress, _reserveCapacity);
_baseAddress = null;
_reserveCapacity = 0;
_committedSize = 0;
_allocatedOffset = 0;
return;
}
var ptr = _baseAddress;
_baseAddress = null;
_allocatedOffset = 0;
_committedSize = 0;
_reserveCapacity = 0;
Munmap(ptr, _reserveCapacity);
}
}

View File

@@ -30,9 +30,6 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
_allocator = allocator;
_handle = handle;
_originalOffset = allocator->_allocatedOffset;
#if MHP_ENABLE_SAFETY_CHECKS
_allocator->_activeScopeCount++;
#endif
}
public void Dispose()
@@ -40,9 +37,6 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
if (_allocator != null)
{
_allocator->_allocatedOffset = _allocator->_allocatedOffset > _originalOffset ? _originalOffset : _allocator->_allocatedOffset;
#if MHP_ENABLE_SAFETY_CHECKS
_allocator->_activeScopeCount--;
#endif
}
}
}
@@ -51,9 +45,6 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
private nuint _reserveCapacity;
private nuint _committedSize;
private nuint _allocatedOffset;
#if MHP_ENABLE_SAFETY_CHECKS
private uint _activeScopeCount;
#endif
public readonly byte* Buffer => _baseAddress;
public readonly nuint Reserved => _reserveCapacity;
@@ -67,10 +58,6 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
_allocatedOffset = 0;
_baseAddress = (byte*)Mmap(null, _reserveCapacity, VirtualAllocationFlags.Reserve);
#if MHP_ENABLE_SAFETY_CHECKS
_activeScopeCount = 0;
#endif
}
/// <summary>
@@ -100,13 +87,6 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
/// </remarks>
public void* Allocate(nuint size, nuint alignment, AllocationOption option = AllocationOption.None)
{
#if MHP_ENABLE_SAFETY_CHECKS
if (_activeScopeCount == 0)
{
throw new InvalidOperationException("Allocations can only be made within an active memory scope.");
}
#endif
if (size == 0)
{
return null;
@@ -157,13 +137,6 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
public void* Reallocate(void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
{
#if MHP_ENABLE_SAFETY_CHECKS
if (_activeScopeCount == 0)
{
throw new InvalidOperationException("Allocations can only be made within an active memory scope.");
}
#endif
if (_baseAddress == null)
{
return null;
@@ -237,14 +210,18 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
public void Dispose()
{
if (_baseAddress != null)
if (_baseAddress == null)
{
Munmap(_baseAddress, _reserveCapacity);
_baseAddress = null;
_reserveCapacity = 0;
_committedSize = 0;
_allocatedOffset = 0;
return;
}
var ptr = _baseAddress;
_baseAddress = null;
_allocatedOffset = 0;
_committedSize = 0;
_reserveCapacity = 0;
Munmap(ptr, _reserveCapacity);
}
}