Refactor memory management with MemoryHandle

Replaced `SafeHandle` with a new `MemoryHandle` system for improved memory tracking, safety, and leak detection. Updated allocators (`ArenaAllocator`, `HeapAllocator`, `StackAllocator`) and collections (`UnTypedArray`, `UnsafeArray<T>`, `UnsafeBitSet`) to use `MemoryHandle`.

Refactored `AllocationManager` to use `ConcurrentSlotMap` for live allocation tracking and added methods for managing `MemoryHandle` instances. Simplified alignment and padding logic across allocators and collections.

Enhanced performance with optimized memory operations (`MemClear`, `MemSet`, `MemCpy`) and vectorized operations in `MemoryUtility` and `UnsafeBitSet`. Fixed alignment issues in vectorized memory operations.

Updated tests to reflect the new memory management system and added new tests for `UnsafeBitSet` bitwise operations. Enabled `ENABLE_COLLECTION_CHECKS` for debug builds and improved error messages and documentation.

Removed unused `SafeHandle` code and adjusted project configuration to include necessary references.
This commit is contained in:
2025-11-25 00:56:21 +09:00
parent 517abd64d6
commit 3269244ab1
15 changed files with 478 additions and 307 deletions

View File

@@ -77,7 +77,8 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
private readonly int _sizeOfTValue;
private readonly int _log2MinGrowth;
private AllocationHandle* _handle;
private MemoryHandle _memoryHandle;
private AllocationHandle* _allocationHandle;
public const int MINIMAL_CAPACITY = 64;
@@ -89,43 +90,22 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
public readonly bool IsEmpty => !IsCreated || _count == 0;
public readonly bool IsCreated
public readonly bool IsCreated => _buffer != null && _allocationHandle != null && _memoryHandle.IsValid;
private static int CalculateDataSize(int capacity, int bucketCapacity, int sizeOfTValue, out int outKeyOffset, out int outNextOffset, out int outBucketOffset)
{
get
{
var handle = SafeHandle.GetSafeHandle(_buffer, (nuint)_alignment);
return handle != null && Volatile.Read(ref handle->valid) == 1;
}
}
var sizeOfTKey = sizeof(TKey);
var sizeOfInt = sizeof(int);
private static int CalculateDataSize(int capacity, int bucketCapacity, int sizeOfTValue, int alignment, out int outValueOffset, out int outKeyOffset, out int outNextOffset, out int outBucketOffset)
{
static int AlignUp(int size, int align)
{
return (size + (align - 1)) & ~(align - 1);
}
var valuesSize = sizeOfTValue * capacity;
var keysSize = sizeOfTKey * capacity;
var nextSize = sizeOfInt * capacity;
var bucketSize = sizeOfInt * bucketCapacity;
var totalSize = valuesSize + keysSize + nextSize + bucketSize;
var headerSize = SafeHandle.GetPaddedHeaderSize((nuint)alignment);
var valuesSize = (sizeOfTValue * capacity);
var valuesSizePadded = AlignUp(valuesSize, alignment);
var keysSize = (sizeof(TKey) * capacity);
var keysSizePadded = AlignUp(keysSize, alignment);
var nextSize = (sizeof(int) * capacity);
var nextSizePadded = AlignUp(nextSize, alignment);
// Buckets are the last item, doesn't need padding after it
var bucketSize = (sizeof(int) * bucketCapacity);
outValueOffset = (int)headerSize;
outKeyOffset = outValueOffset + valuesSizePadded;
outNextOffset = outKeyOffset + keysSizePadded;
outBucketOffset = outNextOffset + nextSizePadded;
// Total size is header + all buffers
var totalSize = (int)headerSize + outKeyOffset + keysSizePadded + nextSizePadded + bucketSize;
outKeyOffset = 0 + valuesSize;
outNextOffset = outKeyOffset + keysSize;
outBucketOffset = outNextOffset + nextSize;
return totalSize;
}
@@ -149,16 +129,16 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
var alignOfInt = (int)AlignOf<int>();
var maxDataAlign = Math.Max(Math.Max(alignOfTValue, alignOfKey), alignOfInt);
_alignment = (int)SafeHandle.GetAlignWithHeader((nuint)maxDataAlign);
_alignment = maxDataAlign;
_sizeOfTValue = sizeOfTValue;
_log2MinGrowth = BitOperations.Log2(minGrowth);
_handle = (AllocationHandle*)Unsafe.AsPointer(ref handle);
_allocationHandle = (AllocationHandle*)Unsafe.AsPointer(ref handle);
var totalSize = CalculateDataSize(_capacity, _bucketCapacity, sizeOfTValue, _alignment,
out var valueOffset, out var keyOffset, out var nextOffset, out var bucketOffset);
var totalSize = CalculateDataSize(_capacity, _bucketCapacity, sizeOfTValue,
out var keyOffset, out var nextOffset, out var bucketOffset);
AllocateBuffer(totalSize, valueOffset, keyOffset, nextOffset, bucketOffset, _alignment, allocationOption);
AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset, allocationOption);
Clear();
}
@@ -210,28 +190,31 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void AllocateBuffer(int totalSize, int valueOffset, int keyOffset, int nextOffset, int bucketOffset, int alignment, AllocationOption allocationOption)
private void AllocateBuffer(int totalSize, int keyOffset, int nextOffset, int bucketOffset, AllocationOption allocationOption)
{
var buf = (byte*)_handle->Alloc(_handle->Allocator, (uint)totalSize, (nuint)alignment, allocationOption);
MemoryHandle memHandle;
var buf = (byte*)_allocationHandle->Alloc(_allocationHandle->Allocator, (uint)totalSize, (nuint)_alignment, allocationOption, &memHandle);
_buffer = buf + valueOffset;
_buffer = buf;
_keys = (TKey*)(_buffer + keyOffset);
_next = (int*)(_buffer + nextOffset);
_buckets = (int*)(_buffer + bucketOffset);
_memoryHandle = memHandle;
}
private void ResizeExact(int newCapacity, int newBucketCapacity)
{
var totalSize = CalculateDataSize(newCapacity, newBucketCapacity, _sizeOfTValue, _alignment,
out var valueOffset, out var keyOffset, out var nextOffset, out var bucketOffset);
var totalSize = CalculateDataSize(newCapacity, newBucketCapacity, _sizeOfTValue,
out var keyOffset, out var nextOffset, out var bucketOffset);
var oldBuffer = _buffer;
var oldKeys = _keys;
var oldNext = _next;
var oldBuckets = _buckets;
var oldBucketCapacity = _bucketCapacity;
var oldMemoryHandle = _memoryHandle;
AllocateBuffer(totalSize, valueOffset, keyOffset, nextOffset, bucketOffset, _alignment, AllocationOption.None);
AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset, AllocationOption.None);
_capacity = newCapacity;
_bucketCapacity = newBucketCapacity;
@@ -246,7 +229,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
}
}
_handle->Free(_handle->Allocator, oldBuffer);
_allocationHandle->Free(_allocationHandle->Allocator, oldBuffer, oldMemoryHandle);
}
public void Resize(int newCapacity)
@@ -540,9 +523,9 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
return;
}
if (_handle != null)
if (_allocationHandle != null)
{
_handle->Free(_handle->Allocator, _buffer);
_allocationHandle->Free(_allocationHandle->Allocator, _buffer, _memoryHandle);
}
_buffer = null;