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:
@@ -1,22 +1,5 @@
|
|||||||
global using static Misaki.HighPerformance.LowLevel.Utilities.MemoryUtility;
|
global using static Misaki.HighPerformance.LowLevel.Utilities.MemoryUtility;
|
||||||
|
|
||||||
global using unsafe AllocFunc = delegate*<void*, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption
|
global using unsafe AllocFunc = delegate*<void*, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption, void*>;
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
global using unsafe ReallocFunc = delegate*<void*, void*, nuint, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption, void*>;
|
||||||
, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle*
|
global using unsafe FreeFunc = delegate*<void*, void*, void>;
|
||||||
#endif
|
|
||||||
, void*>;
|
|
||||||
global using unsafe ReallocFunc = delegate*<void*, void*, nuint, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle*
|
|
||||||
#endif
|
|
||||||
, void*>;
|
|
||||||
global using unsafe FreeFunc = delegate*<void*, void*
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle
|
|
||||||
#endif
|
|
||||||
, void>;
|
|
||||||
global using unsafe IsValidFunc = delegate*<void*
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle
|
|
||||||
#endif
|
|
||||||
, bool>;
|
|
||||||
|
|||||||
@@ -94,27 +94,15 @@ public static unsafe class AllocationManager
|
|||||||
State = null,
|
State = null,
|
||||||
Alloc = &Allocate,
|
Alloc = &Allocate,
|
||||||
Realloc = &Reallocate,
|
Realloc = &Reallocate,
|
||||||
Free = &Free,
|
Free = &Free
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
IsValid = &IsValid
|
|
||||||
#else
|
|
||||||
IsValid = null
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void* Allocate(void* _, nuint size, nuint alignment, AllocationOption allocationOption
|
private static void* Allocate(void* _, nuint size, nuint alignment, AllocationOption allocationOption)
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, MemoryHandle* pHandle
|
|
||||||
#endif
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
var ptr = AlignedAlloc(size, alignment);
|
var ptr = AlignedAlloc(size, alignment);
|
||||||
if (ptr == null)
|
if (ptr == null)
|
||||||
{
|
{
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
*pHandle = MemoryHandle.Invalid;
|
|
||||||
#endif
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,38 +111,12 @@ public static unsafe class AllocationManager
|
|||||||
MemClear(ptr, size);
|
MemClear(ptr, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
*pHandle = AddAllocation(ptr, size);
|
|
||||||
#endif
|
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void* Reallocate(void* _, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption
|
private static void* Reallocate(void* _, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, MemoryHandle* pHandle
|
|
||||||
#endif
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
var newPtr = AlignedRealloc(ptr, newSize, alignment);
|
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)
|
if (newPtr == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
@@ -170,16 +132,9 @@ public static unsafe class AllocationManager
|
|||||||
return newPtr;
|
return newPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Free(void* _, void* ptr
|
private static void Free(void* _, void* ptr)
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, MemoryHandle handle
|
|
||||||
#endif
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
AlignedFree(ptr);
|
AlignedFree(ptr);
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
RemoveAllocation(handle);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
@@ -373,15 +328,23 @@ public static unsafe class AllocationManager
|
|||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
|
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
|
#endif
|
||||||
}
|
}
|
||||||
@@ -501,7 +464,6 @@ public static unsafe class AllocationManager
|
|||||||
if (pStack != null)
|
if (pStack != null)
|
||||||
{
|
{
|
||||||
pStack->Dispose();
|
pStack->Dispose();
|
||||||
Free(pStack);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -159,10 +159,12 @@ public unsafe struct Arena : IMemoryAllocator<Arena, Arena.CreationOptions>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryUtility.Free(_buffer);
|
var ptr = _buffer;
|
||||||
|
|
||||||
_buffer = null;
|
_buffer = null;
|
||||||
_size = 0;
|
_size = 0;
|
||||||
_offset = 0;
|
_offset = 0;
|
||||||
|
|
||||||
|
MemoryUtility.Free(ptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,7 +183,12 @@ public unsafe struct DynamicArena : IMemoryAllocator<DynamicArena, DynamicArena.
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var current = _root;
|
var ptr = _root;
|
||||||
|
|
||||||
|
_root = null;
|
||||||
|
_current = null;
|
||||||
|
|
||||||
|
var current = ptr;
|
||||||
while (current != null)
|
while (current != null)
|
||||||
{
|
{
|
||||||
var next = current->next;
|
var next = current->next;
|
||||||
@@ -191,8 +196,5 @@ public unsafe struct DynamicArena : IMemoryAllocator<DynamicArena, DynamicArena.
|
|||||||
MemoryUtility.Free(current);
|
MemoryUtility.Free(current);
|
||||||
current = next;
|
current = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
_root = null;
|
|
||||||
_current = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
if (_caches != null)
|
||||||
{
|
{
|
||||||
MemoryUtility.Free(_caches);
|
MemoryUtility.Free(_caches);
|
||||||
@@ -739,7 +729,17 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpti
|
|||||||
_instanceId = null;
|
_instanceId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var arena = _chunkArena;
|
||||||
|
var chunk = _chunks;
|
||||||
_chunks = null;
|
_chunks = null;
|
||||||
_cacheCount = 0;
|
|
||||||
|
while (chunk != null)
|
||||||
|
{
|
||||||
|
var next = chunk->next;
|
||||||
|
AlignedFree(chunk->memory);
|
||||||
|
chunk = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
arena.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ using System.Diagnostics.CodeAnalysis;
|
|||||||
|
|
||||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
|
||||||
public readonly struct MemoryHandle : IEquatable<MemoryHandle>
|
public readonly struct MemoryHandle : IDisposable, IEquatable<MemoryHandle>
|
||||||
{
|
{
|
||||||
public readonly int ID
|
public readonly int ID
|
||||||
{
|
{
|
||||||
@@ -16,12 +16,25 @@ public readonly struct MemoryHandle : IEquatable<MemoryHandle>
|
|||||||
|
|
||||||
public static readonly MemoryHandle Invalid = default;
|
public static readonly MemoryHandle Invalid = default;
|
||||||
|
|
||||||
|
public bool IsValid => AllocationManager.ContainsAllocation(this);
|
||||||
|
public bool IsInvalid => !IsValid;
|
||||||
|
|
||||||
public MemoryHandle(int id, int generation)
|
public MemoryHandle(int id, int generation)
|
||||||
{
|
{
|
||||||
ID = id + 1;
|
ID = id + 1;
|
||||||
Generation = generation + 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)
|
public bool Equals(MemoryHandle other)
|
||||||
{
|
{
|
||||||
return ID == other.ID && Generation == other.Generation;
|
return ID == other.ID && Generation == other.Generation;
|
||||||
@@ -42,6 +55,11 @@ public readonly struct MemoryHandle : IEquatable<MemoryHandle>
|
|||||||
return $"MemoryHandle(Id: {ID}, Generation: {Generation})";
|
return $"MemoryHandle(Id: {ID}, Generation: {Generation})";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
AllocationManager.RemoveAllocation(this);
|
||||||
|
}
|
||||||
|
|
||||||
public static bool operator ==(MemoryHandle left, MemoryHandle right)
|
public static bool operator ==(MemoryHandle left, MemoryHandle right)
|
||||||
{
|
{
|
||||||
return left.Equals(right);
|
return left.Equals(right);
|
||||||
@@ -61,7 +79,7 @@ public readonly unsafe struct AllocationHandle
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a pointer to the state instance associated with this allocation handle.
|
/// Gets a pointer to the state instance associated with this allocation handle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void* State
|
public required void* State
|
||||||
{
|
{
|
||||||
get; init;
|
get; init;
|
||||||
}
|
}
|
||||||
@@ -69,7 +87,7 @@ public readonly unsafe struct AllocationHandle
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a function pointer for allocating memory.
|
/// Gets a function pointer for allocating memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public AllocFunc Alloc
|
public required AllocFunc Alloc
|
||||||
{
|
{
|
||||||
get; init;
|
get; init;
|
||||||
}
|
}
|
||||||
@@ -77,7 +95,7 @@ public readonly unsafe struct AllocationHandle
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a function pointer for reallocating memory.
|
/// Gets a function pointer for reallocating memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ReallocFunc Realloc
|
public required ReallocFunc Realloc
|
||||||
{
|
{
|
||||||
get; init;
|
get; init;
|
||||||
}
|
}
|
||||||
@@ -85,15 +103,7 @@ public readonly unsafe struct AllocationHandle
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a function pointer for freeing allocated memory.
|
/// Gets a function pointer for freeing allocated memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public FreeFunc Free
|
public required FreeFunc Free
|
||||||
{
|
|
||||||
get; init;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a function pointer for validating a memory handle.
|
|
||||||
/// </summary>
|
|
||||||
public IsValidFunc IsValid
|
|
||||||
{
|
{
|
||||||
get; init;
|
get; init;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ public unsafe struct MemoryBlock : IDisposable
|
|||||||
private nuint _size;
|
private nuint _size;
|
||||||
private nuint _alignment;
|
private nuint _alignment;
|
||||||
|
|
||||||
private MemoryHandle _memoryHandle;
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
private AllocationHandle _allocationHandle;
|
private readonly MemoryHandle _memoryHandle;
|
||||||
|
#endif
|
||||||
|
private readonly AllocationHandle _allocationHandle;
|
||||||
|
|
||||||
public readonly nuint Size => _size;
|
public readonly nuint Size => _size;
|
||||||
public readonly nuint Alignment => _alignment;
|
public readonly nuint Alignment => _alignment;
|
||||||
@@ -23,14 +25,7 @@ public unsafe struct MemoryBlock : IDisposable
|
|||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
if (_buffer != null)
|
if (_buffer != null)
|
||||||
{
|
{
|
||||||
if (_allocationHandle.IsValid != null)
|
return _memoryHandle.IsValid;
|
||||||
{
|
|
||||||
return _allocationHandle.IsValid(_allocationHandle.State, _memoryHandle);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -54,19 +49,12 @@ public unsafe struct MemoryBlock : IDisposable
|
|||||||
throw new InvalidOperationException("Target allocation handle does not support allocation.");
|
throw new InvalidOperationException("Target allocation handle does not support allocation.");
|
||||||
}
|
}
|
||||||
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
_buffer = handle.Alloc(handle.State, size, alignment, allocationOption);
|
||||||
MemoryHandle memHandle;
|
|
||||||
#endif
|
|
||||||
_buffer = handle.Alloc(handle.State, size, alignment, allocationOption
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, &memHandle
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
_size = size;
|
_size = size;
|
||||||
_alignment = alignment;
|
_alignment = alignment;
|
||||||
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
_memoryHandle = memHandle;
|
_memoryHandle = MemoryHandle.Create(_buffer, _size);
|
||||||
#endif
|
#endif
|
||||||
_allocationHandle = handle;
|
_allocationHandle = handle;
|
||||||
}
|
}
|
||||||
@@ -121,17 +109,10 @@ public unsafe struct MemoryBlock : IDisposable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
_buffer = _allocationHandle.Realloc(_allocationHandle.State, _buffer, _size, newSize, _alignment, option);
|
||||||
var memHandle = _memoryHandle;
|
|
||||||
#endif
|
|
||||||
_buffer = _allocationHandle.Realloc(_allocationHandle.State, _buffer, _size, newSize, _alignment, option
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, &memHandle
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
_size = newSize;
|
_size = newSize;
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
_memoryHandle = memHandle;
|
_memoryHandle.Update(_buffer, _size);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,44 +258,19 @@ public unsafe struct MemoryBlock : IDisposable
|
|||||||
{
|
{
|
||||||
if (!IsCreated)
|
if (!IsCreated)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
UnsafeCollectionUtility.ReportDoubleFree<MemoryBlock>(_buffer);
|
||||||
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
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_allocationHandle.Free != null)
|
if (_allocationHandle.Free != null)
|
||||||
{
|
{
|
||||||
_allocationHandle.Free(_allocationHandle.State, _buffer
|
_allocationHandle.Free(_allocationHandle.State, _buffer);
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, _memoryHandle
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
|
_memoryHandle.Dispose();
|
||||||
|
#endif
|
||||||
|
|
||||||
_buffer = null;
|
_buffer = null;
|
||||||
_size = 0;
|
|
||||||
_alignment = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
@@ -14,72 +13,33 @@ public unsafe struct MemoryPool<TAllocator, TOpts> : IDisposable
|
|||||||
|
|
||||||
public MemoryPool(in TOpts opts)
|
public MemoryPool(in TOpts opts)
|
||||||
{
|
{
|
||||||
_pAllocator = (TAllocator*)Malloc((nuint)sizeof(TAllocator));
|
var allocator = TAllocator.Create(opts);
|
||||||
*_pAllocator = TAllocator.Create(opts);
|
|
||||||
|
_pAllocator = (TAllocator*)allocator.Allocate((nuint)sizeof(TAllocator), AlignOf<TAllocator>(), AllocationOption.None);
|
||||||
|
*_pAllocator = allocator;
|
||||||
|
|
||||||
_allocationHandle = new AllocationHandle
|
_allocationHandle = new AllocationHandle
|
||||||
{
|
{
|
||||||
State = _pAllocator,
|
State = _pAllocator,
|
||||||
Alloc = &Allocate,
|
Alloc = &Allocate,
|
||||||
Realloc = &Reallocate,
|
Realloc = &Reallocate,
|
||||||
Free = &Free,
|
Free = &Free
|
||||||
IsValid = null
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void* Allocate(void* pAllocator, nuint size, nuint alignment, AllocationOption allocationOption
|
private static void* Allocate(void* pAllocator, nuint size, nuint alignment, AllocationOption allocationOption)
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, MemoryHandle* pHandle
|
|
||||||
#endif
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
var ptr = ((TAllocator*)pAllocator)->Allocate(size, alignment, allocationOption);
|
return ((TAllocator*)pAllocator)->Allocate(size, alignment, allocationOption);
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
if (ptr != null)
|
|
||||||
{
|
|
||||||
*pHandle = AllocationManager.AddAllocation(ptr, size);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return ptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void* Reallocate(void* pAllocator, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption 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
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
var newPtr = ((TAllocator*)pAllocator)->Reallocate(ptr, oldSize, newSize, alignment, allocationOption);
|
return ((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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Free(void* pAllocator, void* ptr
|
private static void Free(void* pAllocator, void* ptr)
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, MemoryHandle handle
|
|
||||||
#endif
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
((TAllocator*)pAllocator)->Free(ptr);
|
((TAllocator*)pAllocator)->Free(ptr);
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
AllocationManager.RemoveAllocation(handle);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@@ -91,8 +51,6 @@ public unsafe struct MemoryPool<TAllocator, TOpts> : IDisposable
|
|||||||
|
|
||||||
_pAllocator->Dispose();
|
_pAllocator->Dispose();
|
||||||
|
|
||||||
MemoryUtility.Free(_pAllocator);
|
|
||||||
|
|
||||||
_pAllocator = null;
|
_pAllocator = null;
|
||||||
_allocationHandle = default;
|
_allocationHandle = default;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,9 +33,6 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOptio
|
|||||||
_allocator = allocator;
|
_allocator = allocator;
|
||||||
_handle = handle;
|
_handle = handle;
|
||||||
_originalOffset = allocator->_offset;
|
_originalOffset = allocator->_offset;
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
_allocator->_activeScopeCount++;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@@ -43,9 +40,6 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOptio
|
|||||||
if (_allocator != null)
|
if (_allocator != null)
|
||||||
{
|
{
|
||||||
_allocator->_offset = _allocator->_offset > _originalOffset ? _originalOffset : _allocator->_offset;
|
_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 byte* _buffer;
|
||||||
private nuint _size;
|
private nuint _size;
|
||||||
private nuint _offset;
|
private nuint _offset;
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
private uint _activeScopeCount;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public readonly byte* Buffer => _buffer;
|
public readonly byte* Buffer => _buffer;
|
||||||
public readonly nuint Size => _size;
|
public readonly nuint Size => _size;
|
||||||
@@ -72,9 +63,6 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOptio
|
|||||||
_buffer = (byte*)Malloc(size);
|
_buffer = (byte*)Malloc(size);
|
||||||
_size = size;
|
_size = size;
|
||||||
_offset = 0;
|
_offset = 0;
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
_activeScopeCount = 0;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -101,13 +89,6 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOptio
|
|||||||
/// there is insufficient space in the buffer.</returns>
|
/// there is insufficient space in the buffer.</returns>
|
||||||
public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption = AllocationOption.None)
|
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)
|
if (size == 0)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
@@ -197,10 +178,12 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOptio
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryUtility.Free(_buffer);
|
var ptr = _buffer;
|
||||||
|
|
||||||
_buffer = null;
|
_buffer = null;
|
||||||
_size = 0;
|
|
||||||
_offset = 0;
|
_offset = 0;
|
||||||
|
_size = 0;
|
||||||
|
|
||||||
|
MemoryUtility.Free(ptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -474,6 +474,7 @@ public unsafe struct TLSF : IMemoryAllocator<TLSF, TLSF.CreationOptions>
|
|||||||
MemoryUtility.Free(_blocks);
|
MemoryUtility.Free(_blocks);
|
||||||
_blocks = null;
|
_blocks = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_slBitmaps != null)
|
if (_slBitmaps != null)
|
||||||
{
|
{
|
||||||
MemoryUtility.Free(_slBitmaps);
|
MemoryUtility.Free(_slBitmaps);
|
||||||
@@ -481,14 +482,13 @@ public unsafe struct TLSF : IMemoryAllocator<TLSF, TLSF.CreationOptions>
|
|||||||
}
|
}
|
||||||
|
|
||||||
MemoryChunk* chunk = _chunks;
|
MemoryChunk* chunk = _chunks;
|
||||||
|
_chunks = null;
|
||||||
|
|
||||||
while (chunk != null)
|
while (chunk != null)
|
||||||
{
|
{
|
||||||
MemoryChunk* next = chunk->next;
|
MemoryChunk* next = chunk->next;
|
||||||
AlignedFree(chunk->memory);
|
AlignedFree(chunk->memory);
|
||||||
chunk = next;
|
chunk = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
_chunks = null;
|
|
||||||
_flBitmap = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -232,14 +232,18 @@ public unsafe struct VirtualArena : IMemoryAllocator<VirtualArena, VirtualArena.
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_baseAddress != null)
|
if (_baseAddress == null)
|
||||||
{
|
{
|
||||||
Munmap(_baseAddress, _reserveCapacity);
|
return;
|
||||||
|
|
||||||
_baseAddress = null;
|
|
||||||
_reserveCapacity = 0;
|
|
||||||
_committedSize = 0;
|
|
||||||
_allocatedOffset = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ptr = _baseAddress;
|
||||||
|
|
||||||
|
_baseAddress = null;
|
||||||
|
_allocatedOffset = 0;
|
||||||
|
_committedSize = 0;
|
||||||
|
_reserveCapacity = 0;
|
||||||
|
|
||||||
|
Munmap(ptr, _reserveCapacity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,9 +30,6 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
|
|||||||
_allocator = allocator;
|
_allocator = allocator;
|
||||||
_handle = handle;
|
_handle = handle;
|
||||||
_originalOffset = allocator->_allocatedOffset;
|
_originalOffset = allocator->_allocatedOffset;
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
_allocator->_activeScopeCount++;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@@ -40,9 +37,6 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
|
|||||||
if (_allocator != null)
|
if (_allocator != null)
|
||||||
{
|
{
|
||||||
_allocator->_allocatedOffset = _allocator->_allocatedOffset > _originalOffset ? _originalOffset : _allocator->_allocatedOffset;
|
_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 _reserveCapacity;
|
||||||
private nuint _committedSize;
|
private nuint _committedSize;
|
||||||
private nuint _allocatedOffset;
|
private nuint _allocatedOffset;
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
private uint _activeScopeCount;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public readonly byte* Buffer => _baseAddress;
|
public readonly byte* Buffer => _baseAddress;
|
||||||
public readonly nuint Reserved => _reserveCapacity;
|
public readonly nuint Reserved => _reserveCapacity;
|
||||||
@@ -67,10 +58,6 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
|
|||||||
_allocatedOffset = 0;
|
_allocatedOffset = 0;
|
||||||
|
|
||||||
_baseAddress = (byte*)Mmap(null, _reserveCapacity, VirtualAllocationFlags.Reserve);
|
_baseAddress = (byte*)Mmap(null, _reserveCapacity, VirtualAllocationFlags.Reserve);
|
||||||
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
_activeScopeCount = 0;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -100,13 +87,6 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public void* Allocate(nuint size, nuint alignment, AllocationOption option = AllocationOption.None)
|
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)
|
if (size == 0)
|
||||||
{
|
{
|
||||||
return null;
|
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)
|
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)
|
if (_baseAddress == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
@@ -237,14 +210,18 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_baseAddress != null)
|
if (_baseAddress == null)
|
||||||
{
|
{
|
||||||
Munmap(_baseAddress, _reserveCapacity);
|
return;
|
||||||
|
|
||||||
_baseAddress = null;
|
|
||||||
_reserveCapacity = 0;
|
|
||||||
_committedSize = 0;
|
|
||||||
_allocatedOffset = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ptr = _baseAddress;
|
||||||
|
|
||||||
|
_baseAddress = null;
|
||||||
|
_allocatedOffset = 0;
|
||||||
|
_committedSize = 0;
|
||||||
|
_reserveCapacity = 0;
|
||||||
|
|
||||||
|
Munmap(ptr, _reserveCapacity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,10 +54,6 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
|||||||
|
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This buffer has 4 parts: TValue, TKey, Next, Buckets.
|
// This buffer has 4 parts: TValue, TKey, Next, Buckets.
|
||||||
@@ -99,14 +95,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
|||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
if (_buffer != null)
|
if (_buffer != null)
|
||||||
{
|
{
|
||||||
if (_allocationHandle.IsValid != null)
|
return _memoryHandle.IsValid;
|
||||||
{
|
|
||||||
return _allocationHandle.IsValid(_allocationHandle.State, _memoryHandle);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -156,7 +145,13 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
|||||||
var totalSize = CalculateDataSize(_capacity, _bucketCapacity, sizeOfTValue,
|
var totalSize = CalculateDataSize(_capacity, _bucketCapacity, sizeOfTValue,
|
||||||
out var keyOffset, out var nextOffset, out var bucketOffset);
|
out var keyOffset, out var nextOffset, out var bucketOffset);
|
||||||
|
|
||||||
|
allocationOption &= ~AllocationOption.Clear;
|
||||||
AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset, allocationOption);
|
AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset, allocationOption);
|
||||||
|
|
||||||
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
|
_memoryHandle = MemoryHandle.Create(_buffer, (nuint)totalSize);
|
||||||
|
#endif
|
||||||
|
|
||||||
Clear();
|
Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,22 +251,12 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
|||||||
throw new InvalidOperationException("Target allocation handle does not support allocation.");
|
throw new InvalidOperationException("Target allocation handle does not support allocation.");
|
||||||
}
|
}
|
||||||
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
var buf = (byte*)_allocationHandle.Alloc(_allocationHandle.State, (uint)totalSize, (nuint)_alignment, allocationOption);
|
||||||
MemoryHandle memHandle;
|
|
||||||
#endif
|
|
||||||
var buf = (byte*)_allocationHandle.Alloc(_allocationHandle.State, (uint)totalSize, (nuint)_alignment, allocationOption
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, &memHandle
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
|
|
||||||
_buffer = buf;
|
_buffer = buf;
|
||||||
_keys = (TKey*)(_buffer + keyOffset);
|
_keys = (TKey*)(_buffer + keyOffset);
|
||||||
_next = (int*)(_buffer + nextOffset);
|
_next = (int*)(_buffer + nextOffset);
|
||||||
_buckets = (int*)(_buffer + bucketOffset);
|
_buckets = (int*)(_buffer + bucketOffset);
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
_memoryHandle = memHandle;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResizeExact(int newCapacity, int newBucketCapacity)
|
private void ResizeExact(int newCapacity, int newBucketCapacity)
|
||||||
@@ -284,9 +269,6 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
|||||||
var oldNext = _next;
|
var oldNext = _next;
|
||||||
var oldBuckets = _buckets;
|
var oldBuckets = _buckets;
|
||||||
var oldBucketCapacity = _bucketCapacity;
|
var oldBucketCapacity = _bucketCapacity;
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
var oldMemoryHandle = _memoryHandle;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset, AllocationOption.None);
|
AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset, AllocationOption.None);
|
||||||
_capacity = newCapacity;
|
_capacity = newCapacity;
|
||||||
@@ -305,12 +287,12 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
|||||||
|
|
||||||
if (_allocationHandle.Free != null)
|
if (_allocationHandle.Free != null)
|
||||||
{
|
{
|
||||||
_allocationHandle.Free(_allocationHandle.State, oldBuffer
|
_allocationHandle.Free(_allocationHandle.State, oldBuffer);
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, oldMemoryHandle
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
|
_memoryHandle.Update(_buffer, (nuint)totalSize);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Resize(int newCapacity)
|
public void Resize(int newCapacity)
|
||||||
@@ -719,42 +701,19 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
|||||||
{
|
{
|
||||||
if (!IsCreated)
|
if (!IsCreated)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
UnsafeCollectionUtility.ReportDoubleFree<HashMapHelper<TKey>>(_buffer);
|
||||||
if (_buffer == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var message = "The HashMapHelper 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
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_allocationHandle.Free != null)
|
if (_allocationHandle.Free != null)
|
||||||
{
|
{
|
||||||
_allocationHandle.Free(_allocationHandle.State, _buffer
|
_allocationHandle.Free(_allocationHandle.State, _buffer);
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, _memoryHandle
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
|
_memoryHandle.Dispose();
|
||||||
|
#endif
|
||||||
|
|
||||||
_buffer = null;
|
_buffer = null;
|
||||||
_keys = null;
|
_keys = null;
|
||||||
_next = null;
|
_next = null;
|
||||||
|
|||||||
@@ -108,14 +108,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
|||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
if (_buffer != null)
|
if (_buffer != null)
|
||||||
{
|
{
|
||||||
if (_allocationHandle.IsValid != null)
|
return _memoryHandle.IsValid;
|
||||||
{
|
|
||||||
return _allocationHandle.IsValid(_allocationHandle.State, _memoryHandle);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -149,18 +142,9 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
|||||||
throw new InvalidOperationException("Target allocation handle does not support allocation.");
|
throw new InvalidOperationException("Target allocation handle does not support allocation.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_buffer = (T*)handle.Alloc(handle.State, (nuint)(count * sizeof(T)), AlignOf<T>(), allocationOption);
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
MemoryHandle memHandle;
|
_memoryHandle = MemoryHandle.Create(_buffer, (nuint)(count * sizeof(T)));
|
||||||
#endif
|
|
||||||
var buff = handle.Alloc(handle.State, (nuint)(count * sizeof(T)), AlignOf<T>(), allocationOption
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, &memHandle
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
|
|
||||||
_buffer = (T*)buff;
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
_memoryHandle = memHandle;
|
|
||||||
#endif
|
#endif
|
||||||
_allocationHandle = handle;
|
_allocationHandle = handle;
|
||||||
_count = count;
|
_count = count;
|
||||||
@@ -247,19 +231,12 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
MemoryHandle memHandle = _memoryHandle;
|
|
||||||
#endif
|
|
||||||
var elemSize = SizeOf<T>();
|
var elemSize = SizeOf<T>();
|
||||||
_buffer = (T*)_allocationHandle.Realloc(_allocationHandle.State, _buffer, (nuint)Count * elemSize, (nuint)newSize * elemSize, AlignOf<T>(), option
|
_buffer = (T*)_allocationHandle.Realloc(_allocationHandle.State, _buffer, (nuint)Count * elemSize, (nuint)newSize * elemSize, AlignOf<T>(), option);
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, &memHandle
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
_memoryHandle = memHandle;
|
|
||||||
#endif
|
|
||||||
_count = newSize;
|
_count = newSize;
|
||||||
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
|
_memoryHandle.Update(_buffer, (nuint)newSize * elemSize);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -407,42 +384,19 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
|||||||
{
|
{
|
||||||
if (!IsCreated)
|
if (!IsCreated)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
UnsafeCollectionUtility.ReportDoubleFree<UnsafeArray<T>>(_buffer);
|
||||||
if (_buffer == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var message = "The UnsafeArray 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
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_allocationHandle.Free != null)
|
if (_allocationHandle.Free != null)
|
||||||
{
|
{
|
||||||
_allocationHandle.Free(_allocationHandle.State, _buffer
|
_allocationHandle.Free(_allocationHandle.State, _buffer);
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, _memoryHandle
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
|
_memoryHandle.Dispose();
|
||||||
|
#endif
|
||||||
|
|
||||||
_buffer = null;
|
_buffer = null;
|
||||||
_count = 0;
|
_count = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,361 @@
|
|||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A dynamically resizing, parallel, lock-free queue using unmanaged chunks.
|
||||||
|
/// Uses a very brief spin lock only during chunk allocation, alongside a lock-free segment cache.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe struct UnsafeChunkedQueue<T> : IDisposable
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct ChunkSlot
|
||||||
|
{
|
||||||
|
// 0 = Empty, 1 = Ready (Writer has finished writing)
|
||||||
|
public int state;
|
||||||
|
public T value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct ChunkHeader
|
||||||
|
{
|
||||||
|
public ChunkHeader* next;
|
||||||
|
public ChunkHeader* nextFree;
|
||||||
|
public int capacity;
|
||||||
|
|
||||||
|
// Cache line padding to avoid false sharing between atomic counters
|
||||||
|
private readonly long _pad1, _pad2, _pad3;
|
||||||
|
public int head;
|
||||||
|
|
||||||
|
private readonly long _pad4, _pad5, _pad6;
|
||||||
|
public int tail;
|
||||||
|
|
||||||
|
private readonly long _pad7, _pad8, _pad9;
|
||||||
|
public int consumedSlots;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly unsafe struct ParallelProducer
|
||||||
|
{
|
||||||
|
private readonly UnsafeChunkedQueue<T>* _queue;
|
||||||
|
|
||||||
|
internal ParallelProducer(UnsafeChunkedQueue<T>* queue)
|
||||||
|
{
|
||||||
|
_queue = queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Enqueue(T item) => _queue->Enqueue(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly unsafe struct ParallelConsumer
|
||||||
|
{
|
||||||
|
private readonly UnsafeChunkedQueue<T>* _queue;
|
||||||
|
|
||||||
|
internal ParallelConsumer(UnsafeChunkedQueue<T>* queue)
|
||||||
|
{
|
||||||
|
_queue = queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool TryDequeue(out T item) => _queue->TryDequeue(out item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pointer representations (nint utilized for straightforward Interlocked compatibility)
|
||||||
|
private nint _head;
|
||||||
|
private nint _tail;
|
||||||
|
private nint _freeList;
|
||||||
|
|
||||||
|
private int _expandLock;
|
||||||
|
|
||||||
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
|
private readonly MemoryHandle _memoryHandle;
|
||||||
|
#endif
|
||||||
|
private readonly AllocationHandle _allocHandle;
|
||||||
|
private readonly AllocationOption _allocOption;
|
||||||
|
private readonly int _chunkCapacity;
|
||||||
|
|
||||||
|
public readonly bool IsCreated => _head != 0;
|
||||||
|
|
||||||
|
public UnsafeChunkedQueue(int capacityPerChunk, AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
|
||||||
|
{
|
||||||
|
_chunkCapacity = Math.Max(32, capacityPerChunk);
|
||||||
|
_allocHandle = handle;
|
||||||
|
_allocOption = allocationOption;
|
||||||
|
_freeList = 0;
|
||||||
|
_expandLock = 0;
|
||||||
|
|
||||||
|
// Preallocate the first chunk
|
||||||
|
var initialChunk = AllocateNewChunk();
|
||||||
|
_head = (nint)initialChunk;
|
||||||
|
_tail = (nint)initialChunk;
|
||||||
|
|
||||||
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
|
_memoryHandle = MemoryHandle.Create(initialChunk, (nuint)(_chunkCapacity * sizeof(ChunkSlot)));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnsafeChunkedQueue(int capacityPerChunk, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
|
||||||
|
: this(capacityPerChunk, AllocationManager.GetAllocationHandle(allocator), allocationOption)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Try to enqueue an item. Expands automatically if the current chunk is full.
|
||||||
|
/// </summary>
|
||||||
|
public void Enqueue(T item)
|
||||||
|
{
|
||||||
|
SpinWait spin = new SpinWait();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
ChunkHeader* tail = (ChunkHeader*)_tail;
|
||||||
|
|
||||||
|
// Reserve our slot
|
||||||
|
int tailIdx = Interlocked.Increment(ref tail->tail) - 1;
|
||||||
|
|
||||||
|
if (tailIdx < tail->capacity)
|
||||||
|
{
|
||||||
|
// Slot secured. Let's write.
|
||||||
|
var slot = (ChunkSlot*)(tail + 1) + tailIdx;
|
||||||
|
slot->value = item;
|
||||||
|
Volatile.Write(ref slot->state, 1); // Mark as readable
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chunk is full. Expand the queue.
|
||||||
|
if (Interlocked.CompareExchange(ref _expandLock, 1, 0) == 0)
|
||||||
|
{
|
||||||
|
// Verify no other thread already expanded
|
||||||
|
if (tail == (ChunkHeader*)_tail)
|
||||||
|
{
|
||||||
|
var newChunk = GetChunkFromPoolOrAllocate();
|
||||||
|
|
||||||
|
// Pre-write our object onto the new chunk's first spot safely
|
||||||
|
newChunk->tail = 1;
|
||||||
|
var slot = (ChunkSlot*)(newChunk + 1) + 0;
|
||||||
|
slot->value = item;
|
||||||
|
Volatile.Write(ref slot->state, 1);
|
||||||
|
|
||||||
|
// Attach new chunk
|
||||||
|
Volatile.Write(ref *(nint*)&tail->next, (nint)newChunk);
|
||||||
|
Volatile.Write(ref _tail, (nint)newChunk);
|
||||||
|
Volatile.Write(ref _expandLock, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Volatile.Write(ref _expandLock, 0); // Release if another thread expanded
|
||||||
|
}
|
||||||
|
|
||||||
|
// Another thread is allocating the chunk. Spin and retry.
|
||||||
|
spin.SpinOnce();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to dequeue an item.
|
||||||
|
/// </summary>
|
||||||
|
public bool TryDequeue(out T item)
|
||||||
|
{
|
||||||
|
SpinWait spin = new SpinWait();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
ChunkHeader* head = (ChunkHeader*)Volatile.Read(ref _head);
|
||||||
|
if (head == null)
|
||||||
|
{
|
||||||
|
item = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int currentHead = Volatile.Read(ref head->head);
|
||||||
|
int currentTail = Volatile.Read(ref head->tail);
|
||||||
|
|
||||||
|
if (currentHead >= head->capacity)
|
||||||
|
{
|
||||||
|
// Current chunk exhausted. Advance _head to Next chunk.
|
||||||
|
ChunkHeader* next = (ChunkHeader*)Volatile.Read(ref *(nint*)&head->next);
|
||||||
|
if (next != null)
|
||||||
|
{
|
||||||
|
if (Interlocked.CompareExchange(ref _head, (nint)next, (nint)head) == (nint)head)
|
||||||
|
{
|
||||||
|
// Successfully unlinked this chunk from _head.
|
||||||
|
// If all slots have already been read, recycle it safely now!
|
||||||
|
if (Volatile.Read(ref head->consumedSlots) >= head->capacity)
|
||||||
|
{
|
||||||
|
RecycleChunk(head);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We reached the end of the chunks, but a writer might be locking to expand right now.
|
||||||
|
if (Volatile.Read(ref _expandLock) == 1)
|
||||||
|
{
|
||||||
|
spin.SpinOnce();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
item = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent infinite loop: if head has caught up to tail, the queue chunk is empty.
|
||||||
|
if (currentHead >= currentTail)
|
||||||
|
{
|
||||||
|
item = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to acquire the slot at currentHead lock-free
|
||||||
|
if (Interlocked.CompareExchange(ref head->head, currentHead + 1, currentHead) == currentHead)
|
||||||
|
{
|
||||||
|
var slot = (ChunkSlot*)(head + 1) + currentHead;
|
||||||
|
|
||||||
|
// Wait until the Enqueuing thread has finished writing (usually 0 spins)
|
||||||
|
SpinWait innerWait = new SpinWait();
|
||||||
|
while (Volatile.Read(ref slot->state) == 0)
|
||||||
|
{
|
||||||
|
innerWait.SpinOnce();
|
||||||
|
}
|
||||||
|
|
||||||
|
item = slot->value;
|
||||||
|
|
||||||
|
// Track how many values have been permanently read
|
||||||
|
int consumed = Interlocked.Increment(ref head->consumedSlots);
|
||||||
|
|
||||||
|
// We recycle only if all readers are done AND this chunk is already detached from _head
|
||||||
|
// (prevents ABA object reuse crashes where _head still points to a recycled memory block).
|
||||||
|
if (consumed >= head->capacity && Volatile.Read(ref _head) != (nint)head)
|
||||||
|
{
|
||||||
|
RecycleChunk(head);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private ChunkHeader* GetChunkFromPoolOrAllocate()
|
||||||
|
{
|
||||||
|
// Pop lock-free from the free list
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
ChunkHeader* free = (ChunkHeader*)Volatile.Read(ref _freeList);
|
||||||
|
if (free == null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextFree = free->nextFree;
|
||||||
|
if (Interlocked.CompareExchange(ref _freeList, (nint)nextFree, (nint)free) == (nint)free)
|
||||||
|
{
|
||||||
|
// Reset chunk
|
||||||
|
free->next = null;
|
||||||
|
free->nextFree = null;
|
||||||
|
free->head = 0;
|
||||||
|
free->tail = 0;
|
||||||
|
free->consumedSlots = 0;
|
||||||
|
|
||||||
|
var slots = (ChunkSlot*)(free + 1);
|
||||||
|
MemClear(slots, (uint)(_chunkCapacity * sizeof(ChunkSlot)));
|
||||||
|
return free;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return AllocateNewChunk();
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly ChunkHeader* AllocateNewChunk()
|
||||||
|
{
|
||||||
|
nuint byteSize = (nuint)sizeof(ChunkHeader) + (nuint)(_chunkCapacity * sizeof(ChunkSlot));
|
||||||
|
ChunkHeader* block = (ChunkHeader*)_allocHandle.Alloc(_allocHandle.State, byteSize, AlignOf<int>(), _allocOption);
|
||||||
|
|
||||||
|
block->next = null;
|
||||||
|
block->nextFree = null;
|
||||||
|
block->capacity = _chunkCapacity;
|
||||||
|
block->head = 0;
|
||||||
|
block->tail = 0;
|
||||||
|
block->consumedSlots = 0;
|
||||||
|
|
||||||
|
var slots = (ChunkSlot*)(block + 1);
|
||||||
|
MemClear(slots, (uint)(_chunkCapacity * sizeof(ChunkSlot)));
|
||||||
|
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RecycleChunk(ChunkHeader* chunk)
|
||||||
|
{
|
||||||
|
// Push lock-free to the free list
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
ChunkHeader* free = (ChunkHeader*)Volatile.Read(ref _freeList);
|
||||||
|
chunk->nextFree = free;
|
||||||
|
if (Interlocked.CompareExchange(ref _freeList, (nint)chunk, (nint)free) == (nint)free)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a parallel producer for this queue. The returned struct contains a raw pointer
|
||||||
|
/// to the queue and can be used from multiple threads as long as the queue struct itself
|
||||||
|
/// remains alive and its address stable.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ParallelProducer AsParallelProducer()
|
||||||
|
{
|
||||||
|
return new ParallelProducer((UnsafeChunkedQueue<T>*)Unsafe.AsPointer(ref this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a parallel consumer for this queue. The returned struct contains a raw pointer
|
||||||
|
/// to the queue and can be used from multiple threads as long as the queue struct itself
|
||||||
|
/// remains alive and its address stable.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ParallelConsumer AsParallelConsumer()
|
||||||
|
{
|
||||||
|
return new ParallelConsumer((UnsafeChunkedQueue<T>*)Unsafe.AsPointer(ref this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (!IsCreated)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispose Active Chunks
|
||||||
|
ChunkHeader* curr = (ChunkHeader*)_head;
|
||||||
|
while (curr != null)
|
||||||
|
{
|
||||||
|
var next = curr->next;
|
||||||
|
_allocHandle.Free(_allocHandle.State, curr);
|
||||||
|
curr = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispose FreeList cache Chunks
|
||||||
|
ChunkHeader* free = (ChunkHeader*)_freeList;
|
||||||
|
while (free != null)
|
||||||
|
{
|
||||||
|
var next = free->nextFree;
|
||||||
|
_allocHandle.Free(_allocHandle.State, free);
|
||||||
|
free = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
|
_memoryHandle.Dispose();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_head = 0;
|
||||||
|
_tail = 0;
|
||||||
|
_freeList = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
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.Utilities;
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
using System.Collections;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
<Authors>Misaki</Authors>
|
<Authors>Misaki</Authors>
|
||||||
<AssemblyVersion>1.6.11</AssemblyVersion>
|
<AssemblyVersion>1.6.12</AssemblyVersion>
|
||||||
<Version>$(AssemblyVersion)</Version>
|
<Version>$(AssemblyVersion)</Version>
|
||||||
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>
|
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>
|
||||||
<RepositoryUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</RepositoryUrl>
|
<RepositoryUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</RepositoryUrl>
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
<DefineConstants>$(DefineConstants);MHP_ENABLE_SAFETY_CHECKS</DefineConstants>
|
<DefineConstants>$(DefineConstants);MHP_ENABLE_SAFETY_CHECKS;MHP_ENABLE_STACKTRACE</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Misaki.HighPerformance.LowLevel.Utilities;
|
namespace Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
@@ -8,8 +9,35 @@ namespace Misaki.HighPerformance.LowLevel.Utilities;
|
|||||||
/// Provides extension methods for copying elements between unsafe collections and spans, converting collections to
|
/// Provides extension methods for copying elements between unsafe collections and spans, converting collections to
|
||||||
/// arrays or lists, and searching for values.
|
/// arrays or lists, and searching for values.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static unsafe class UnsafeCollectionExtensions
|
public static unsafe class UnsafeCollectionUtility
|
||||||
{
|
{
|
||||||
|
[Conditional("DEBUG")]
|
||||||
|
internal static void ReportDoubleFree<T>(void* buffer)
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = $"The {typeof(T).Name} 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);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts a managed array to an UnsafeArray by copying its elements to unmanaged memory.
|
/// Converts a managed array to an UnsafeArray by copying its elements to unmanaged memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -3,10 +3,10 @@ using Misaki.HighPerformance.LowLevel.Collections;
|
|||||||
|
|
||||||
//BenchmarkRunner.Run<SPMDBenchmark>();
|
//BenchmarkRunner.Run<SPMDBenchmark>();
|
||||||
|
|
||||||
AllocationManager.Initialize(AllocationManagerInitOpts.Default);
|
//AllocationManager.Initialize(AllocationManagerInitOpts.Default);
|
||||||
var set = new UnsafeBitSet(100, Allocator.Persistent, AllocationOption.Clear);
|
//var set = new UnsafeBitSet(100, Allocator.Persistent, AllocationOption.Clear);
|
||||||
set.SetBit(0);
|
//set.SetBit(0);
|
||||||
Console.WriteLine(set.NextSetBit(0));
|
//Console.WriteLine(set.NextSetBit(0));
|
||||||
|
|
||||||
set.Dispose();
|
//set.Dispose();
|
||||||
AllocationManager.Dispose();
|
//AllocationManager.Dispose();
|
||||||
@@ -63,11 +63,9 @@ public class TestAllocationManager
|
|||||||
var ptr1 = new MemoryBlock(1024, 8, scope.AllocationHandle);
|
var ptr1 = new MemoryBlock(1024, 8, scope.AllocationHandle);
|
||||||
|
|
||||||
Assert.IsTrue(ptr1.IsCreated);
|
Assert.IsTrue(ptr1.IsCreated);
|
||||||
Assert.AreEqual(1024u, ((VirtualStack*)scope.AllocationHandle.State)->Allocated);
|
|
||||||
|
|
||||||
ptr1.Dispose();
|
ptr1.Dispose();
|
||||||
scope.Dispose();
|
scope.Dispose();
|
||||||
Assert.AreEqual(0u, ((VirtualStack*)scope.AllocationHandle.State)->Allocated);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
thread.Start();
|
thread.Start();
|
||||||
@@ -76,10 +74,10 @@ public class TestAllocationManager
|
|||||||
var ptr2 = new MemoryBlock(1024, 8, scope.AllocationHandle);
|
var ptr2 = new MemoryBlock(1024, 8, scope.AllocationHandle);
|
||||||
|
|
||||||
Assert.IsTrue(ptr2.IsCreated);
|
Assert.IsTrue(ptr2.IsCreated);
|
||||||
Assert.AreEqual(1024u, ((VirtualStack*)scope.AllocationHandle.State)->Allocated);
|
|
||||||
|
|
||||||
ptr2.Dispose();
|
ptr2.Dispose();
|
||||||
scope.Dispose();
|
scope.Dispose();
|
||||||
Assert.AreEqual(0u, ((VirtualStack*)scope.AllocationHandle.State)->Allocated);
|
|
||||||
|
thread.Join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,7 +186,6 @@ public unsafe class TestArena
|
|||||||
}
|
}
|
||||||
|
|
||||||
Assert.IsTrue(arena.Buffer == null);
|
Assert.IsTrue(arena.Buffer == null);
|
||||||
Assert.AreEqual(0u, (uint)arena.Size);
|
|
||||||
Assert.IsNull(arena.Allocate(8, 8, AllocationOption.None));
|
Assert.IsNull(arena.Allocate(8, 8, AllocationOption.None));
|
||||||
arena.Dispose();
|
arena.Dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,22 +144,6 @@ public unsafe class TestStack
|
|||||||
Assert.IsNull(stack.Buffer);
|
Assert.IsNull(stack.Buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
[TestMethod]
|
|
||||||
public void Stack_AllocationFailsOutsideScope()
|
|
||||||
{
|
|
||||||
var stack = new Stack(128 * 1024);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Assert.ThrowsExactly<InvalidOperationException>(() => stack.Allocate(32, 8, AllocationOption.Clear));
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
stack.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Stack_InvalidAlignment_Throws()
|
public void Stack_InvalidAlignment_Throws()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -145,25 +145,6 @@ public unsafe class TestVirtualStack
|
|||||||
Assert.IsNull(stack.Buffer);
|
Assert.IsNull(stack.Buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
[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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void VirtualStack_InvalidAlignment_Throws()
|
public void VirtualStack_InvalidAlignment_Throws()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
|
||||||
|
namespace Misaki.HighPerformance.Test.UnitTest.Collections;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class TestUnsafeChunkedQueue
|
||||||
|
{
|
||||||
|
public TestContext TestContext
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void BasicEnqueueDequeueTest()
|
||||||
|
{
|
||||||
|
using var queue = new UnsafeChunkedQueue<int>(32, Allocator.Persistent);
|
||||||
|
|
||||||
|
Assert.IsTrue(queue.IsCreated);
|
||||||
|
|
||||||
|
queue.Enqueue(10);
|
||||||
|
queue.Enqueue(20);
|
||||||
|
|
||||||
|
Assert.IsTrue(queue.TryDequeue(out var val1));
|
||||||
|
Assert.AreEqual(10, val1);
|
||||||
|
|
||||||
|
Assert.IsTrue(queue.TryDequeue(out var val2));
|
||||||
|
Assert.AreEqual(20, val2);
|
||||||
|
|
||||||
|
Assert.IsFalse(queue.TryDequeue(out _));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ChunkExpansionTest()
|
||||||
|
{
|
||||||
|
// Force chunk expansions by enqueuing more than the chunk capacity
|
||||||
|
using var queue = new UnsafeChunkedQueue<int>(16, Allocator.Persistent);
|
||||||
|
|
||||||
|
var totalItems = 100;
|
||||||
|
|
||||||
|
for (var i = 0; i < totalItems; i++)
|
||||||
|
{
|
||||||
|
queue.Enqueue(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < totalItems; i++)
|
||||||
|
{
|
||||||
|
Assert.IsTrue(queue.TryDequeue(out var val));
|
||||||
|
Assert.AreEqual(i, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.IsFalse(queue.TryDequeue(out _));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ConcurrentEnqueueDequeueTest()
|
||||||
|
{
|
||||||
|
// Multi-threaded stress test to verify lock-free safety and chunk caching
|
||||||
|
using var queue = new UnsafeChunkedQueue<int>(64, Allocator.Persistent);
|
||||||
|
var totalElements = 100_000;
|
||||||
|
|
||||||
|
var enqueueTask = Task.Run(() =>
|
||||||
|
{
|
||||||
|
Parallel.For(0, totalElements, i =>
|
||||||
|
{
|
||||||
|
queue.Enqueue(i);
|
||||||
|
});
|
||||||
|
}, TestContext.CancellationToken);
|
||||||
|
|
||||||
|
long sum = 0;
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
var dequeueTask = Task.Run(() =>
|
||||||
|
{
|
||||||
|
while (Volatile.Read(ref count) < totalElements)
|
||||||
|
{
|
||||||
|
if (queue.TryDequeue(out var val))
|
||||||
|
{
|
||||||
|
Interlocked.Add(ref sum, val);
|
||||||
|
Interlocked.Increment(ref count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, TestContext.CancellationToken);
|
||||||
|
|
||||||
|
Task.WaitAll(enqueueTask, dequeueTask);
|
||||||
|
|
||||||
|
Assert.AreEqual(totalElements, count);
|
||||||
|
|
||||||
|
// Sum of 0..N-1 is N * (N - 1) / 2
|
||||||
|
var expectedSum = (long)totalElements * (totalElements - 1) / 2;
|
||||||
|
Assert.AreEqual(expectedSum, sum);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,10 +17,7 @@ public class TestUnsafeHashSet
|
|||||||
[TestCleanup]
|
[TestCleanup]
|
||||||
public void Cleanup()
|
public void Cleanup()
|
||||||
{
|
{
|
||||||
if (_set.IsCreated)
|
_set.Dispose();
|
||||||
{
|
|
||||||
_set.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|||||||
Reference in New Issue
Block a user