This commit is contained in:
2026-03-30 12:47:29 +09:00
parent 04dd7222d9
commit 8231d6df60
45 changed files with 2497 additions and 707 deletions

View File

@@ -11,7 +11,7 @@ namespace Misaki.HighPerformance.Image;
/// <remarks>The image data is stored as a contiguous block of unmanaged memory and must be released by calling /// <remarks>The image data is stored as a contiguous block of unmanaged memory and must be released by calling
/// <see cref="Dispose"/> when no longer needed. Be careful that this struct won't stop your double free if you copy it. /// <see cref="Dispose"/> when no longer needed. Be careful that this struct won't stop your double free if you copy it.
/// Ensure to have proper ownership management when using this struct.</remarks> /// Ensure to have proper ownership management when using this struct.</remarks>
public unsafe readonly struct ImageResult : IDisposable public readonly unsafe struct ImageResult : IDisposable
{ {
public byte* Data public byte* Data
{ {

View File

@@ -11,7 +11,7 @@ namespace Misaki.HighPerformance.Image;
/// <remarks>The image data is stored as a contiguous block of unmanaged memory and must be released by calling /// <remarks>The image data is stored as a contiguous block of unmanaged memory and must be released by calling
/// <see cref="Dispose"/> when no longer needed. Be careful that this struct won't stop your double free if you copy it. /// <see cref="Dispose"/> when no longer needed. Be careful that this struct won't stop your double free if you copy it.
/// Ensure to have proper ownership management when using this struct.</remarks> /// Ensure to have proper ownership management when using this struct.</remarks>
public unsafe readonly struct ImageResultFloat : IDisposable public readonly unsafe struct ImageResultFloat : IDisposable
{ {
public float* Data public float* Data
{ {

View File

@@ -1,6 +1,6 @@
using System.Runtime.CompilerServices;
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.LowLevel.Utilities;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Jobs; namespace Misaki.HighPerformance.Jobs;
@@ -28,7 +28,7 @@ public unsafe struct TempJobAllocator : IAllocator, IDisposable
_currentFrameIndex = 0; _currentFrameIndex = 0;
_memoryHandle = memoryHandle; _memoryHandle = memoryHandle;
for (int i = 0; i < _FRAME_LATENCY; i++) for (var i = 0; i < _FRAME_LATENCY; i++)
{ {
_pArena[i] = new VirtualArena(capacity); _pArena[i] = new VirtualArena(capacity);
_allocationsPerFrame[i] = 0; _allocationsPerFrame[i] = 0;
@@ -75,7 +75,7 @@ public unsafe struct TempJobAllocator : IAllocator, IDisposable
return null; return null;
} }
MemoryUtility.MemCpy(ptr, newPtr,Math.Min(oldSize, newSize)); MemoryUtility.MemCpy(ptr, newPtr, Math.Min(oldSize, newSize));
return newPtr; return newPtr;
} }
@@ -90,7 +90,7 @@ public unsafe struct TempJobAllocator : IAllocator, IDisposable
private static bool IsValid(void* instance, MemoryHandle handle) private static bool IsValid(void* instance, MemoryHandle handle)
{ {
var pSelf = (TempJobAllocator*)instance; var pSelf = (TempJobAllocator*)instance;
return handle.id == _MAGIC_ID && handle.generation > pSelf->_currentFrameCount - _FRAME_LATENCY; return handle.ID == _MAGIC_ID && handle.Generation > pSelf->_currentFrameCount - _FRAME_LATENCY;
} }
public int AdvanceFrame() public int AdvanceFrame()
@@ -107,7 +107,7 @@ public unsafe struct TempJobAllocator : IAllocator, IDisposable
public void Dispose() public void Dispose()
{ {
for (int i = 0; i < _FRAME_LATENCY; i++) for (var i = 0; i < _FRAME_LATENCY; i++)
{ {
_pArena[i].Dispose(); _pArena[i].Dispose();
} }

View File

@@ -1,4 +1,3 @@
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections.Concurrent; using System.Collections.Concurrent;
namespace Misaki.HighPerformance.Jobs; namespace Misaki.HighPerformance.Jobs;

View File

@@ -1,6 +1,22 @@
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, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle*, void*>; global using unsafe AllocFunc = delegate*<void*, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption
global using unsafe ReallocFunc = delegate*<void*, void*, nuint, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle*, void*>; #if ENABLE_SAFETY_CHECKS
global using unsafe FreeFunc = delegate*<void*, void*, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle, void>; , Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle*
global using unsafe IsValidFunc = delegate*<void*, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle, bool>; #endif
, void*>;
global using unsafe ReallocFunc = delegate*<void*, void*, nuint, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption
#if ENABLE_SAFETY_CHECKS
, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle*
#endif
, void*>;
global using unsafe FreeFunc = delegate*<void*, void*
#if ENABLE_SAFETY_CHECKS
, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle
#endif
, void>;
global using unsafe IsValidFunc = delegate*<void*
#if ENABLE_SAFETY_CHECKS
, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle
#endif
, bool>;

View File

@@ -1,4 +1,8 @@
#define ENABLE_DEBUG_LAYER
#if ENABLE_SAFETY_CHECKS
using Misaki.HighPerformance.Collections; using Misaki.HighPerformance.Collections;
#endif
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
#if ENABLE_DEBUG_LAYER #if ENABLE_DEBUG_LAYER
@@ -13,20 +17,30 @@ namespace Misaki.HighPerformance.LowLevel.Buffer;
public readonly struct AllocationInfo public readonly struct AllocationInfo
{ {
/// <summary> /// <summary>
/// Get the size of the allocation in bytes. /// Gets the address of the allocated memory block.
/// </summary>
public IntPtr Address
{
get; init;
}
/// <summary>
/// Gets the size of the allocation in bytes.
/// </summary> /// </summary>
public nuint Size public nuint Size
{ {
get; init; get; init;
} }
#if ENABLE_DEBUG_LAYER
/// <summary> /// <summary>
/// Get the stack trace at the time of allocation for debugging purposes. /// Gets the stack trace at the time of allocation for debugging purposes.
/// </summary> /// </summary>
public StackTrace StackTrace public GCHandle StackTrace
{ {
get; init; get; init;
} }
#endif
} }
public readonly struct AllocationManagerInitOpts public readonly struct AllocationManagerInitOpts
@@ -85,31 +99,51 @@ public static unsafe class AllocationManager
Alloc = &Allocate, Alloc = &Allocate,
Realloc = &Reallocate, Realloc = &Reallocate,
Free = null, Free = null,
#if ENABLE_SAFETY_CHECKS
IsValid = &IsValid IsValid = &IsValid
#else
IsValid = null
#endif
}; };
_currentTick = 0; _currentTick = 0;
} }
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle) private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption
#if ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle
#endif
)
{ {
var selfPtr = (ArenaAllocator*)instance; var selfPtr = (ArenaAllocator*)instance;
var ptr = selfPtr->_arena.Allocate(size, alignment, allocationOption); var ptr = selfPtr->_arena.Allocate(size, alignment, allocationOption);
if (ptr == null) if (ptr == null)
{ {
#if ENABLE_SAFETY_CHECKS
*pHandle = MemoryHandle.Invalid; *pHandle = MemoryHandle.Invalid;
#endif
return null; return null;
} }
#if ENABLE_SAFETY_CHECKS
*pHandle = new MemoryHandle(_ARENA_MAGIC_ID, selfPtr->_currentTick); *pHandle = new MemoryHandle(_ARENA_MAGIC_ID, selfPtr->_currentTick);
#endif
return ptr; return ptr;
} }
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle) private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption
#if ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle
#endif
)
{ {
if (ptr == null) if (ptr == null)
{ {
return Allocate(instance, newSize, alignment, allocationOption, pHandle); return Allocate(instance, newSize, alignment, allocationOption
#if ENABLE_SAFETY_CHECKS
, pHandle
#endif
);
} }
var selfPtr = (ArenaAllocator*)instance; var selfPtr = (ArenaAllocator*)instance;
@@ -121,15 +155,19 @@ public static unsafe class AllocationManager
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize)); MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
#if ENABLE_SAFETY_CHECKS
*pHandle = new MemoryHandle(_ARENA_MAGIC_ID, selfPtr->_currentTick); *pHandle = new MemoryHandle(_ARENA_MAGIC_ID, selfPtr->_currentTick);
#endif
return newPtr; return newPtr;
} }
#if ENABLE_SAFETY_CHECKS
private static bool IsValid(void* instance, MemoryHandle handle) private static bool IsValid(void* instance, MemoryHandle handle)
{ {
var selfPtr = (ArenaAllocator*)instance; var selfPtr = (ArenaAllocator*)instance;
return handle.id == _ARENA_MAGIC_ID && handle.generation == selfPtr->_currentTick; return handle.ID == _ARENA_MAGIC_ID && handle.Generation == selfPtr->_currentTick;
} }
#endif
public void Reset() public void Reset()
{ {
@@ -157,20 +195,42 @@ public static unsafe class AllocationManager
Alloc = &Allocate, Alloc = &Allocate,
Realloc = &Reallocate, Realloc = &Reallocate,
Free = &Free, Free = &Free,
#if ENABLE_SAFETY_CHECKS
IsValid = &IsValid IsValid = &IsValid
#else
IsValid = null
#endif
}; };
} }
private static void* Allocate(void* _, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle) private static void* Allocate(void* _, nuint size, nuint alignment, AllocationOption allocationOption
#if ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle
#endif
)
{ {
return HeapAlloc(size, alignment, allocationOption, pHandle); return HeapAlloc(size, alignment, allocationOption
#if ENABLE_SAFETY_CHECKS
, pHandle
#else
, default
#endif
);
} }
private static void* Reallocate(void* _, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle) private static void* Reallocate(void* _, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption
#if ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle
#endif
)
{ {
if (ptr == null) if (ptr == null)
{ {
return Allocate(null, newSize, alignment, allocationOption, pHandle); return Allocate(null, newSize, alignment, allocationOption
#if ENABLE_SAFETY_CHECKS
, pHandle
#endif
);
} }
MemoryHandle newHandle; MemoryHandle newHandle;
@@ -181,21 +241,41 @@ public static unsafe class AllocationManager
} }
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize)); MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
HeapFree(ptr, *pHandle); HeapFree(ptr
#if ENABLE_SAFETY_CHECKS
, *pHandle
#else
, default
#endif
);
#if ENABLE_SAFETY_CHECKS
*pHandle = newHandle; *pHandle = newHandle;
#endif
return newPtr; return newPtr;
} }
private static void Free(void* _, void* ptr, MemoryHandle handle) private static void Free(void* _, void* ptr
#if ENABLE_SAFETY_CHECKS
, MemoryHandle handle
#endif
)
{ {
HeapFree(ptr, handle); HeapFree(ptr
#if ENABLE_SAFETY_CHECKS
, handle
#else
, default
#endif
);
} }
#if ENABLE_SAFETY_CHECKS
private static bool IsValid(void* _, MemoryHandle handle) private static bool IsValid(void* _, MemoryHandle handle)
{ {
return ContainsAllocation(handle); return ContainsAllocation(handle);
} }
#endif
} }
private struct StackAllocator : IAllocator, IDisposable private struct StackAllocator : IAllocator, IDisposable
@@ -221,7 +301,11 @@ public static unsafe class AllocationManager
Alloc = &Allocate, Alloc = &Allocate,
Realloc = &Reallocate, Realloc = &Reallocate,
Free = null, Free = null,
#if ENABLE_SAFETY_CHECKS
IsValid = &IsValid IsValid = &IsValid
#else
IsValid = null
#endif
}; };
} }
@@ -264,26 +348,42 @@ public static unsafe class AllocationManager
} }
} }
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle) private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption
#if ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle
#endif
)
{ {
EnsureInitialize(); EnsureInitialize();
var ptr = s_stack.Allocate(size, alignment, allocationOption); var ptr = s_stack.Allocate(size, alignment, allocationOption);
if (ptr == null) if (ptr == null)
{ {
#if ENABLE_SAFETY_CHECKS
*pHandle = MemoryHandle.Invalid; *pHandle = MemoryHandle.Invalid;
#endif
return null; return null;
} }
#if ENABLE_SAFETY_CHECKS
*pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset); *pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset);
#endif
return ptr; return ptr;
} }
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle) private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption
#if ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle
#endif
)
{ {
if (ptr == null) if (ptr == null)
{ {
return Allocate(instance, newSize, alignment, allocationOption, pHandle); return Allocate(instance, newSize, alignment, allocationOption
#if ENABLE_SAFETY_CHECKS
, pHandle
#endif
);
} }
EnsureInitialize(); EnsureInitialize();
@@ -302,7 +402,9 @@ public static unsafe class AllocationManager
} }
} }
#if ENABLE_SAFETY_CHECKS
*pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset); *pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset);
#endif
return ptr; return ptr;
} }
@@ -314,14 +416,18 @@ public static unsafe class AllocationManager
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize)); MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
#if ENABLE_SAFETY_CHECKS
*pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset); *pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset);
#endif
return newPtr; return newPtr;
} }
#if ENABLE_SAFETY_CHECKS
private static bool IsValid(void* instance, MemoryHandle handle) private static bool IsValid(void* instance, MemoryHandle handle)
{ {
return handle.id == _STACK_MAGIC_ID && handle.generation <= (int)s_stack.Offset; return handle.ID == _STACK_MAGIC_ID && handle.Generation <= (int)s_stack.Offset;
} }
#endif
public static VirtualStack.Scope CreateScope(StackAllocator* pSelf) public static VirtualStack.Scope CreateScope(StackAllocator* pSelf)
{ {
@@ -359,11 +465,19 @@ public static unsafe class AllocationManager
Alloc = &Allocate, Alloc = &Allocate,
Realloc = &Reallocate, Realloc = &Reallocate,
Free = &Free, Free = &Free,
#if ENABLE_SAFETY_CHECKS
IsValid = &IsValid IsValid = &IsValid
#else
IsValid = null
#endif
}; };
} }
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle) private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption
#if ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle
#endif
)
{ {
var selfPtr = (FreeListAllocator*)instance; var selfPtr = (FreeListAllocator*)instance;
#if ENABLE_DEBUG_LAYER #if ENABLE_DEBUG_LAYER
@@ -390,26 +504,35 @@ public static unsafe class AllocationManager
MemClear(user, size); MemClear(user, size);
} }
*pHandle = AddAllocation(user); *pHandle = AddAllocation(user, size);
return user; return user;
#else #else
var ptr = selfPtr->_freeList.Allocate(size, alignment, allocationOption); var ptr = selfPtr->_freeList.Allocate(size, alignment, allocationOption);
if (ptr == null) if (ptr == null)
{ {
*pHandle = MemoryHandle.Invalid;
return null; return null;
} }
*pHandle = AddAllocation(ptr); #if ENABLE_SAFETY_CHECKS
*pHandle = AddAllocation(ptr, size);
#endif
return ptr; return ptr;
#endif #endif
} }
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle) private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption
#if ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle
#endif
)
{ {
if (ptr == null) if (ptr == null)
{ {
return Allocate(instance, newSize, alignment, allocationOption, pHandle); return Allocate(instance, newSize, alignment, allocationOption
#if ENABLE_SAFETY_CHECKS
, pHandle
#endif
);
} }
var selfPtr = (FreeListAllocator*)instance; var selfPtr = (FreeListAllocator*)instance;
@@ -445,7 +568,7 @@ public static unsafe class AllocationManager
selfPtr->_freeList.Free(oldHeader->basePtr); selfPtr->_freeList.Free(oldHeader->basePtr);
RemoveAllocation(*pHandle); RemoveAllocation(*pHandle);
*pHandle = AddAllocation(newUser); *pHandle = AddAllocation(newUser, newSize);
return newUser; return newUser;
#else #else
var newPtr = selfPtr->_freeList.Allocate(newSize, alignment, allocationOption); var newPtr = selfPtr->_freeList.Allocate(newSize, alignment, allocationOption);
@@ -457,19 +580,28 @@ public static unsafe class AllocationManager
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize)); MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
selfPtr->_freeList.Free(ptr); selfPtr->_freeList.Free(ptr);
RemoveAllocation(*pHandle);
*pHandle = AddAllocation(newPtr); #if ENABLE_SAFETY_CHECKS
RemoveAllocation(*pHandle);
*pHandle = AddAllocation(newPtr, newSize);
#endif
return newPtr; return newPtr;
#endif #endif
} }
#if ENABLE_SAFETY_CHECKS
private static bool IsValid(void* instance, MemoryHandle handle) private static bool IsValid(void* instance, MemoryHandle handle)
{ {
return ContainsAllocation(handle); return ContainsAllocation(handle);
} }
#endif
private static void Free(void* instance, void* ptr, MemoryHandle handle) private static void Free(void* instance, void* ptr
#if ENABLE_SAFETY_CHECKS
, MemoryHandle handle
#endif
)
{ {
var selfPtr = (FreeListAllocator*)instance; var selfPtr = (FreeListAllocator*)instance;
#if ENABLE_DEBUG_LAYER #if ENABLE_DEBUG_LAYER
@@ -477,10 +609,13 @@ public static unsafe class AllocationManager
UnlinkHeader(header); UnlinkHeader(header);
HeaderFreeHandle(header); HeaderFreeHandle(header);
selfPtr->_freeList.Free(header->basePtr); selfPtr->_freeList.Free(header->basePtr);
RemoveAllocation(handle);
#else #else
selfPtr->_freeList.Free(ptr); selfPtr->_freeList.Free(ptr);
#endif #if ENABLE_SAFETY_CHECKS
RemoveAllocation(handle); RemoveAllocation(handle);
#endif
#endif
} }
public void Dispose() public void Dispose()
@@ -499,17 +634,18 @@ public static unsafe class AllocationManager
private static AllocationHeader* s_pLiveHead; private static AllocationHeader* s_pLiveHead;
#endif #endif
private static ConcurrentSlotMap<IntPtr> s_allocations = null!; #if ENABLE_SAFETY_CHECKS
private static ConcurrentSlotMap<AllocationInfo> s_allocations = null!;
private static bool s_initialized;
private static nuint s_threadLocalStackDefaultSize;
public static readonly MemoryHandle MagicHandle = new MemoryHandle(int.MinValue, int.MinValue); public static readonly MemoryHandle MagicHandle = new MemoryHandle(int.MinValue, int.MinValue);
/// <summary> /// <summary>
/// Gets the number of live tracked heap allocations. /// Gets the number of live tracked heap allocations.
/// </summary> /// </summary>
public static int LiveAllocationCount => s_allocations.Count; public static int LiveAllocationCount => s_allocations.Count;
#endif
private static bool s_initialized;
private static nuint s_threadLocalStackDefaultSize;
public static void Initialize(AllocationManagerInitOpts opts) public static void Initialize(AllocationManagerInitOpts opts)
{ {
@@ -522,8 +658,9 @@ public static unsafe class AllocationManager
s_liveLock = new SpinLock(false); s_liveLock = new SpinLock(false);
s_pLiveHead = null; s_pLiveHead = null;
#endif #endif
#if ENABLE_SAFETY_CHECKS
s_allocations = new ConcurrentSlotMap<IntPtr>(256); s_allocations = new ConcurrentSlotMap<AllocationInfo>(256);
#endif
var ptr = (byte*)Malloc((nuint)(sizeof(ArenaAllocator) + sizeof(HeapAllocator) + sizeof(StackAllocator) + sizeof(FreeListAllocator))); var ptr = (byte*)Malloc((nuint)(sizeof(ArenaAllocator) + sizeof(HeapAllocator) + sizeof(StackAllocator) + sizeof(FreeListAllocator)));
@@ -747,7 +884,7 @@ public static unsafe class AllocationManager
MemClear(ptr, size); MemClear(ptr, size);
} }
*pHandle = AddAllocation(ptr); *pHandle = AddAllocation(ptr, size);
return ptr; return ptr;
} }
@@ -772,7 +909,9 @@ public static unsafe class AllocationManager
AlignedFree(ptr); AlignedFree(ptr);
} }
#if ENABLE_DEBUG_LAYER
RemoveAllocation(handle); RemoveAllocation(handle);
#endif
} }
/// <summary> /// <summary>
@@ -813,40 +952,71 @@ public static unsafe class AllocationManager
/// <summary> /// <summary>
/// Registers a memory allocation and returns a handle that can be used to manage or reference the allocated memory. /// Registers a memory allocation and returns a handle that can be used to manage or reference the allocated memory.
/// </summary> /// </summary>
/// <remarks>
/// Always returns an invalid handle if ENABLE_SAFETY_CHECKS is disabled.
/// </remarks>
/// <param name="ptr">A pointer to the memory block to be registered. The pointer must reference a valid, allocated memory region.</param> /// <param name="ptr">A pointer to the memory block to be registered. The pointer must reference a valid, allocated memory region.</param>
/// <returns>A MemoryHandle representing the registered allocation.</returns> /// <returns>A MemoryHandle representing the registered allocation.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryHandle AddAllocation(void* ptr) public static MemoryHandle AddAllocation(void* ptr, nuint size)
{ {
#if ENABLE_SAFETY_CHECKS
Debug.Assert(s_initialized, "AllocationManager is not initialized."); Debug.Assert(s_initialized, "AllocationManager is not initialized.");
var id = s_allocations.Add((nint)ptr, out var generation); var info = new AllocationInfo
{
Address = (IntPtr)ptr,
Size = size,
#if ENABLE_DEBUG_LAYER
StackTrace = GCHandle.Alloc(new StackTrace(1, true))
#endif
};
var id = s_allocations.Add(info, out var generation);
return new MemoryHandle(id, generation); return new MemoryHandle(id, generation);
#else
return MemoryHandle.Invalid;
#endif
} }
/// <summary> /// <summary>
/// Removes the memory allocation associated with the specified handle. /// Removes the memory allocation associated with the specified handle.
/// </summary> /// </summary>
/// <remarks>
/// Always returns false if debug layer is disabled.
/// </remarks>
/// <param name="handle">The handle representing the memory allocation to remove. The handle must be valid and previously allocated.</param> /// <param name="handle">The handle representing the memory allocation to remove. The handle must be valid and previously allocated.</param>
/// <returns>true if the allocation was successfully removed; otherwise, false.</returns> /// <returns>true if the allocation was successfully removed; otherwise, false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool RemoveAllocation(MemoryHandle handle) public static bool RemoveAllocation(MemoryHandle handle)
{ {
#if ENABLE_SAFETY_CHECKS
Debug.Assert(s_initialized, "AllocationManager is not initialized."); Debug.Assert(s_initialized, "AllocationManager is not initialized.");
return s_allocations.Remove(handle.id, handle.generation); return s_allocations.Remove(handle.ID, handle.Generation);
#else
return false;
#endif
} }
/// <summary> /// <summary>
/// Attempts to retrieve the memory allocation pointer associated with the specified handle. /// Attempts to retrieve the memory allocation pointer associated with the specified handle.
/// </summary> /// </summary>
/// <remarks>
/// Always returns false if debug layer is disabled, and the output pointer will be set to <see cref="IntPtr.Zero"/>.
/// </remarks>
/// <param name="handle">The memory handle identifying the allocation to retrieve allocation.</param> /// <param name="handle">The memory handle identifying the allocation to retrieve allocation.</param>
/// <param name="ptr">When this method returns, contains the pointer to the memory allocation if found; otherwise, <see cref="IntPtr.Zero"/>.</param> /// <param name="ptr">When this method returns, contains the pointer to the memory allocation if found; otherwise, <see cref="IntPtr.Zero"/>.</param>
/// <returns>true if the allocation was found and <paramref name="ptr"/> contains a valid pointer; otherwise, false.</returns> /// <returns>true if the allocation was found and <paramref name="ptr"/> contains a valid pointer; otherwise, false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetAllocation(MemoryHandle handle, out IntPtr ptr) public static bool TryGetAllocation(MemoryHandle handle, out IntPtr ptr)
{ {
#if ENABLE_SAFETY_CHECKS
Debug.Assert(s_initialized, "AllocationManager is not initialized."); Debug.Assert(s_initialized, "AllocationManager is not initialized.");
return s_allocations.TryGetElement(handle.id, handle.generation, out ptr); return s_allocations.TryGetElement(handle.ID, handle.Generation, out ptr);
#else
ptr = IntPtr.Zero;
return false;
#endif
} }
/// <summary> /// <summary>
@@ -855,12 +1025,14 @@ public static unsafe class AllocationManager
/// <remarks> /// <remarks>
/// This only validates the memory when you added the allocation via <see cref="AddAllocation(IntPtr)"/>. /// This only validates the memory when you added the allocation via <see cref="AddAllocation(IntPtr)"/>.
/// For validating memory from <see cref="AllocationHandle"/>, use <see cref="AllocationHandle.IsValid"/> instead. /// For validating memory from <see cref="AllocationHandle"/>, use <see cref="AllocationHandle.IsValid"/> instead.
/// Always returns false if debug layer is disabled.
/// </remarks> /// </remarks>
/// <param name="handle">The memory handle to check for an associated allocation.</param> /// <param name="handle">The memory handle to check for an associated allocation.</param>
/// <returns>true if the allocation corresponding to the handle exists; otherwise, false.</returns> /// <returns>true if the allocation corresponding to the handle exists; otherwise, false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ContainsAllocation(MemoryHandle handle) public static bool ContainsAllocation(MemoryHandle handle)
{ {
#if ENABLE_SAFETY_CHECKS
Debug.Assert(s_initialized, "AllocationManager is not initialized."); Debug.Assert(s_initialized, "AllocationManager is not initialized.");
if (handle == MagicHandle) if (handle == MagicHandle)
@@ -868,7 +1040,10 @@ public static unsafe class AllocationManager
return true; return true;
} }
return s_allocations.Contains(handle.id, handle.generation); return s_allocations.Contains(handle.ID, handle.Generation);
#else
return false;
#endif
} }
/// <summary> /// <summary>
@@ -920,12 +1095,12 @@ public static unsafe class AllocationManager
{ {
throw new MemoryLeakException(CollectionsMarshal.AsSpan(snapshot)); throw new MemoryLeakException(CollectionsMarshal.AsSpan(snapshot));
} }
#endif
Debug.Assert(LiveAllocationCount == 0); #if ENABLE_SAFETY_CHECKS
#else if (s_allocations.Count != 0)
if (LiveAllocationCount != 0)
{ {
throw new MemoryLeakException($"Found {LiveAllocationCount} memory lakes! Please enable debug layer for more informations."); throw new InvalidOperationException($"There are still {s_allocations.Count} live tracked allocations.");
} }
#endif #endif

View File

@@ -4,20 +4,27 @@ namespace Misaki.HighPerformance.LowLevel.Buffer;
public readonly struct MemoryHandle : IEquatable<MemoryHandle> public readonly struct MemoryHandle : IEquatable<MemoryHandle>
{ {
public readonly int id; public readonly int ID
public readonly int generation; {
get => field - 1;
}
public readonly static MemoryHandle Invalid = new(-1, -1); public readonly int Generation
{
get => field - 1;
}
public static readonly MemoryHandle Invalid = default;
public MemoryHandle(int id, int generation) public MemoryHandle(int id, int generation)
{ {
this.id = id; ID = id + 1;
this.generation = generation; Generation = generation + 1;
} }
public bool Equals(MemoryHandle other) public bool Equals(MemoryHandle other)
{ {
return id == other.id && generation == other.generation; return ID == other.ID && Generation == other.Generation;
} }
public override bool Equals([NotNullWhen(true)] object? obj) public override bool Equals([NotNullWhen(true)] object? obj)
@@ -27,12 +34,12 @@ public readonly struct MemoryHandle : IEquatable<MemoryHandle>
public override int GetHashCode() public override int GetHashCode()
{ {
return id ^ generation; return ID ^ Generation;
} }
public override string? ToString() public override string? ToString()
{ {
return $"MemoryHandle(Id: {id}, Generation: {generation})"; return $"MemoryHandle(Id: {ID}, Generation: {Generation})";
} }
public static bool operator ==(MemoryHandle left, MemoryHandle right) public static bool operator ==(MemoryHandle left, MemoryHandle right)

View File

@@ -1,4 +1,3 @@
using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Buffer; namespace Misaki.HighPerformance.LowLevel.Buffer;

View File

@@ -7,6 +7,9 @@ public unsafe interface IUnsafeCollection : IDisposable
/// <summary> /// <summary>
/// Indicates whether the object has been created. Returns true if the object is created, otherwise false. /// Indicates whether the object has been created. Returns true if the object is created, otherwise false.
/// </summary> /// </summary>
/// <remarks>
/// If ENABLE_DEBUG_LAYER is not defined, this property will only check if the underlying pointer is not null, which may not be sufficient to determine if the collection is fully initialized and ready for use.
/// </remarks>
bool IsCreated bool IsCreated
{ {
get; get;

View File

@@ -1,6 +1,5 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
namespace Misaki.HighPerformance.LowLevel.Collections; namespace Misaki.HighPerformance.LowLevel.Collections;

View File

@@ -1,7 +1,6 @@
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.LowLevel.Utilities;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -78,7 +77,9 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
private readonly int _sizeOfTValue; private readonly int _sizeOfTValue;
private readonly int _log2MinGrowth; private readonly int _log2MinGrowth;
#if ENABLE_DEBUG_LAYER
private MemoryHandle _memoryHandle; private MemoryHandle _memoryHandle;
#endif
private AllocationHandle _allocationHandle; private AllocationHandle _allocationHandle;
public const int MINIMAL_CAPACITY = 64; public const int MINIMAL_CAPACITY = 64;
@@ -95,6 +96,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
{ {
get get
{ {
#if ENABLE_DEBUG_LAYER
if (_buffer != null) if (_buffer != null)
{ {
if (_allocationHandle.IsValid != null) if (_allocationHandle.IsValid != null)
@@ -108,6 +110,9 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
} }
return false; return false;
#else
return _buffer != null;
#endif
} }
} }
@@ -251,14 +256,22 @@ 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 ENABLE_DEBUG_LAYER
MemoryHandle memHandle; MemoryHandle memHandle;
var buf = (byte*)_allocationHandle.Alloc(_allocationHandle.State, (uint)totalSize, (nuint)_alignment, allocationOption, &memHandle); #endif
var buf = (byte*)_allocationHandle.Alloc(_allocationHandle.State, (uint)totalSize, (nuint)_alignment, allocationOption
#if ENABLE_DEBUG_LAYER
, &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 ENABLE_DEBUG_LAYER
_memoryHandle = memHandle; _memoryHandle = memHandle;
#endif
} }
private void ResizeExact(int newCapacity, int newBucketCapacity) private void ResizeExact(int newCapacity, int newBucketCapacity)
@@ -271,7 +284,9 @@ 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 ENABLE_DEBUG_LAYER
var oldMemoryHandle = _memoryHandle; var oldMemoryHandle = _memoryHandle;
#endif
AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset, AllocationOption.None); AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset, AllocationOption.None);
_capacity = newCapacity; _capacity = newCapacity;
@@ -290,7 +305,11 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
if (_allocationHandle.Free != null) if (_allocationHandle.Free != null)
{ {
_allocationHandle.Free(_allocationHandle.State, oldBuffer, oldMemoryHandle); _allocationHandle.Free(_allocationHandle.State, oldBuffer
#if ENABLE_DEBUG_LAYER
, oldMemoryHandle
#endif
);
} }
} }
@@ -705,7 +724,11 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
if (_allocationHandle.Free != null) if (_allocationHandle.Free != null)
{ {
_allocationHandle.Free(_allocationHandle.State, _buffer, _memoryHandle); _allocationHandle.Free(_allocationHandle.State, _buffer
#if ENABLE_DEBUG_LAYER
, _memoryHandle
#endif
);
} }
_buffer = null; _buffer = null;

View File

@@ -143,4 +143,9 @@ public readonly unsafe struct ReadOnlyUnsafeCollection<T> : IEnumerable<T>
{ {
return _buffer; return _buffer;
} }
public static implicit operator ReadOnlySpan<T>(ReadOnlyUnsafeCollection<T> collection)
{
return collection.AsSpan();
}
} }

View File

@@ -109,7 +109,7 @@ public unsafe struct UnTypedArray : IUnTypedCollection
return; return;
} }
MemoryHandle memHandle = _memoryHandle; var memHandle = _memoryHandle;
_buffer = _allocationHandle.Realloc(_allocationHandle.State, _buffer, _size, newSize, _alignment, option, &memHandle); _buffer = _allocationHandle.Realloc(_allocationHandle.State, _buffer, _size, newSize, _alignment, option, &memHandle);
_size = newSize; _size = newSize;
_memoryHandle = memHandle; _memoryHandle = memHandle;

View File

@@ -3,9 +3,7 @@ using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections; using System.Collections;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace Misaki.HighPerformance.LowLevel.Collections; namespace Misaki.HighPerformance.LowLevel.Collections;
@@ -26,7 +24,7 @@ internal class UnsafeArrayDebugView<T>
{ {
var count = _array.Count; var count = _array.Count;
var result = new T[count]; var result = new T[count];
for (int i = 0; i < count; i++) for (var i = 0; i < count; i++)
{ {
result[i] = _array[i]; result[i] = _array[i];
} }
@@ -78,7 +76,9 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
private T* _buffer; private T* _buffer;
private int _count; private int _count;
#if ENABLE_DEBUG_LAYER
private MemoryHandle _memoryHandle; private MemoryHandle _memoryHandle;
#endif
private AllocationHandle _allocationHandle; private AllocationHandle _allocationHandle;
public readonly int Count => _count; public readonly int Count => _count;
@@ -108,6 +108,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
{ {
get get
{ {
#if ENABLE_DEBUG_LAYER
if (_buffer != null) if (_buffer != null)
{ {
if (_allocationHandle.IsValid != null) if (_allocationHandle.IsValid != null)
@@ -121,6 +122,9 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
} }
return false; return false;
#else
return _buffer != null;
#endif
} }
} }
@@ -163,11 +167,19 @@ 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.");
} }
#if ENABLE_DEBUG_LAYER
MemoryHandle memHandle; MemoryHandle memHandle;
var buff = handle.Alloc(handle.State, (nuint)(count * sizeof(T)), AlignOf<T>(), allocationOption, &memHandle); #endif
var buff = handle.Alloc(handle.State, (nuint)(count * sizeof(T)), AlignOf<T>(), allocationOption
#if ENABLE_DEBUG_LAYER
, &memHandle
#endif
);
_buffer = (T*)buff; _buffer = (T*)buff;
#if ENABLE_DEBUG_LAYER
_memoryHandle = memHandle; _memoryHandle = memHandle;
#endif
_allocationHandle = handle; _allocationHandle = handle;
_count = count; _count = count;
} }
@@ -246,10 +258,18 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
return; return;
} }
#if ENABLE_DEBUG_LAYER
MemoryHandle memHandle = _memoryHandle; 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, &memHandle); _buffer = (T*)_allocationHandle.Realloc(_allocationHandle.State, _buffer, (nuint)Count * elemSize, (nuint)newSize * elemSize, AlignOf<T>(), option
#if ENABLE_DEBUG_LAYER
, &memHandle
#endif
);
#if ENABLE_DEBUG_LAYER
_memoryHandle = memHandle; _memoryHandle = memHandle;
#endif
_count = newSize; _count = newSize;
} }
@@ -403,7 +423,11 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
if (_allocationHandle.Free != null) if (_allocationHandle.Free != null)
{ {
_allocationHandle.Free(_allocationHandle.State, _buffer, _memoryHandle); _allocationHandle.Free(_allocationHandle.State, _buffer
#if ENABLE_DEBUG_LAYER
, _memoryHandle
#endif
);
} }
_buffer = null; _buffer = null;

View File

@@ -1,5 +1,4 @@
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Diagnostics; using System.Diagnostics;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -40,7 +39,7 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
private UnsafeBitSet _bitSet; private UnsafeBitSet _bitSet;
private int _currentBit; private int _currentBit;
public Iterator (UnsafeBitSet bitSet) public Iterator(UnsafeBitSet bitSet)
{ {
_bitSet = bitSet; _bitSet = bitSet;
_currentBit = -1; _currentBit = -1;
@@ -116,7 +115,7 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
public UnsafeBitSet(Span<uint> bits, Allocator allocator) public UnsafeBitSet(Span<uint> bits, Allocator allocator)
{ {
_bits = new UnsafeArray<uint>(bits.Length, allocator, AllocationOption.None); _bits = new UnsafeArray<uint>(bits.Length, allocator, AllocationOption.None);
_bits.CopyFrom<UnsafeArray<uint>, uint>(bits); _bits.CopyFrom(bits);
_highestBit = 0; _highestBit = 0;
_max = _bits.Count * (_BIT_SIZE + 1) - 1; // Calculate the maximum index in use _max = _bits.Count * (_BIT_SIZE + 1) - 1; // Calculate the maximum index in use
@@ -135,9 +134,9 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
} }
/// <summary> /// <summary>
/// Determines the required length of an <see cref="UnsafeBitSet"/> to hold the passed id or bit. /// Determines the required length of an <see cref="UnsafeBitSet"/> to hold the passed ID or bit.
/// </summary> /// </summary>
/// <param name="id">The id or bit.</param> /// <param name="id">The ID or bit.</param>
/// <returns>A size of required <see cref="uint"/>s for the bitset.</returns> /// <returns>A size of required <see cref="uint"/>s for the bitset.</returns>
public static int RequiredLength(int id) public static int RequiredLength(int id)
{ {
@@ -788,14 +787,14 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
return !(left == right); return !(left == right);
} }
public readonly override int GetHashCode() public override readonly int GetHashCode()
{ {
var hash = new HashCode(); var hash = new HashCode();
hash.AddBytes(MemoryMarshal.AsBytes(_bits.AsSpan())); hash.AddBytes(MemoryMarshal.AsBytes(_bits.AsSpan()));
return hash.ToHashCode(); return hash.ToHashCode();
} }
public readonly override string ToString() public override readonly string ToString()
{ {
// Convert uint to binary form for pretty printing // Convert uint to binary form for pretty printing
var binaryBuilder = new StringBuilder(); var binaryBuilder = new StringBuilder();

View File

@@ -2,7 +2,6 @@ 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.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Collections; namespace Misaki.HighPerformance.LowLevel.Collections;

View File

@@ -35,7 +35,7 @@ internal class UnsafeSlotMapDebugView<T>
/// <summary> /// <summary>
/// Provides an unsafe, high-performance slot map for storing and managing unmanaged values, supporting fast insertion, /// Provides an unsafe, high-performance slot map for storing and managing unmanaged values, supporting fast insertion,
/// removal, and lookup by slot index and generation. /// removal, and lookup by slot index and Generation.
/// </summary> /// </summary>
/// <typeparam name="T">The type of value to store in the slot map. Must be unmanaged.</typeparam> /// <typeparam name="T">The type of value to store in the slot map. Must be unmanaged.</typeparam>
[DebuggerTypeProxy(typeof(UnsafeSlotMapDebugView<>))] [DebuggerTypeProxy(typeof(UnsafeSlotMapDebugView<>))]
@@ -155,7 +155,7 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
/// Adds the specified item to the collection and returns the index of the slot where it was stored. /// Adds the specified item to the collection and returns the index of the slot where it was stored.
/// </summary> /// </summary>
/// <param name="item">The item to add to the collection.</param> /// <param name="item">The item to add to the collection.</param>
/// <param name="generation">When this method returns, contains the generation number associated with the slot where the item was stored.</param> /// <param name="generation">When this method returns, contains the Generation number associated with the slot where the item was stored.</param>
/// <returns>The index of the slot in which the item was stored.</returns> /// <returns>The index of the slot in which the item was stored.</returns>
public int Add(T item, out int generation) public int Add(T item, out int generation)
{ {
@@ -184,10 +184,10 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
} }
/// <summary> /// <summary>
/// Attempts to remove the item at the specified slot index and generation from the collection. /// Attempts to remove the item at the specified slot index and Generation from the collection.
/// </summary> /// </summary>
/// <param name="slotIndex">The zero-based index of the slot to remove. Must be within the valid range of slot indices.</param> /// <param name="slotIndex">The zero-based index of the slot to remove. Must be within the valid range of slot indices.</param>
/// <param name="generation">The generation value associated with the slot. Removal succeeds only if this matches the current generation of the slot.</param> /// <param name="generation">The Generation value associated with the slot. Removal succeeds only if this matches the current Generation of the slot.</param>
/// <param name="item">When this method returns, contains the item that was removed if the removal was successful; otherwise, the default value for type <typeparamref name="T"/>.</param> /// <param name="item">When this method returns, contains the item that was removed if the removal was successful; otherwise, the default value for type <typeparamref name="T"/>.</param>
/// <returns>true if the item was successfully removed; otherwise, false.</returns> /// <returns>true if the item was successfully removed; otherwise, false.</returns>
public bool Remove(int slotIndex, int generation, out T item) public bool Remove(int slotIndex, int generation, out T item)
@@ -216,10 +216,10 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
} }
/// <summary> /// <summary>
/// Attempts to remove the item at the specified slot index and generation from the collection. /// Attempts to remove the item at the specified slot index and Generation from the collection.
/// </summary> /// </summary>
/// <param name="slotIndex">The zero-based index of the slot to remove. Must be within the valid range of slot indices.</param> /// <param name="slotIndex">The zero-based index of the slot to remove. Must be within the valid range of slot indices.</param>
/// <param name="generation">The generation value associated with the slot. Removal succeeds only if this matches the current generation of /// <param name="generation">The Generation value associated with the slot. Removal succeeds only if this matches the current Generation of
/// the slot.</param> /// the slot.</param>
/// <returns>true if the item was successfully removed; otherwise, false.</returns> /// <returns>true if the item was successfully removed; otherwise, false.</returns>
public bool Remove(int slotIndex, int generation) public bool Remove(int slotIndex, int generation)
@@ -228,11 +228,11 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
} }
/// <summary> /// <summary>
/// Determines whether the specified slot index contains a valid entry with the given generation. /// Determines whether the specified slot index contains a valid entry with the given Generation.
/// </summary> /// </summary>
/// <param name="slotIndex">The zero-based index of the slot to check. Must be greater than or equal to 0 and less than the current capacity.</param> /// <param name="slotIndex">The zero-based index of the slot to check. Must be greater than or equal to 0 and less than the current capacity.</param>
/// <param name="generation">The generation value to compare against the slot's generation.</param> /// <param name="generation">The Generation value to compare against the slot's Generation.</param>
/// <returns>true if the slot at the specified index is valid and its generation matches the specified value; otherwise, false.</returns> /// <returns>true if the slot at the specified index is valid and its Generation matches the specified value; otherwise, false.</returns>
public readonly bool Contains(int slotIndex, int generation) public readonly bool Contains(int slotIndex, int generation)
{ {
if (slotIndex < 0 || slotIndex >= _capacity) if (slotIndex < 0 || slotIndex >= _capacity)
@@ -249,14 +249,14 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
} }
/// <summary> /// <summary>
/// Attempts to retrieve the element at the specified slot index and generation. /// Attempts to retrieve the element at the specified slot index and Generation.
/// </summary> /// </summary>
/// <param name="slotIndex">The zero-based index of the slot to retrieve. Must be within the valid range of slots.</param> /// <param name="slotIndex">The zero-based index of the slot to retrieve. Must be within the valid range of slots.</param>
/// <param name="generation">The generation identifier associated with the slot. Used to verify that the slot has not been replaced or /// <param name="generation">The Generation identifier associated with the slot. Used to verify that the slot has not been replaced or
/// invalidated.</param> /// invalidated.</param>
/// <param name="value">When this method returns, contains the element at the specified slot and generation if found; otherwise, the /// <param name="value">When this method returns, contains the element at the specified slot and Generation if found; otherwise, the
/// default value for type <typeparamref name="T"/>.</param> /// default value for type <typeparamref name="T"/>.</param>
/// <returns>true if the element at the specified slot index and generation is found; otherwise, false.</returns> /// <returns>true if the element at the specified slot index and Generation is found; otherwise, false.</returns>
public readonly bool TryGetElementAt(int slotIndex, int generation, out T value) public readonly bool TryGetElementAt(int slotIndex, int generation, out T value)
{ {
if (!Contains(slotIndex, generation)) if (!Contains(slotIndex, generation))
@@ -270,13 +270,13 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
} }
/// <summary> /// <summary>
/// Retrieves the element stored at the specified slot index and generation. /// Retrieves the element stored at the specified slot index and Generation.
/// </summary> /// </summary>
/// <param name="slotIndex">The zero-based index of the slot from which to retrieve the element. Must be within the valid range of allocated slots.</param> /// <param name="slotIndex">The zero-based index of the slot from which to retrieve the element. Must be within the valid range of allocated slots.</param>
/// <param name="generation">The generation identifier associated with the slot. Used to ensure the element has not been replaced or removed since allocation.</param> /// <param name="generation">The Generation identifier associated with the slot. Used to ensure the element has not been replaced or removed since allocation.</param>
/// <returns>The element of type <see cref="T"/> stored at the specified slot and generation.</returns> /// <returns>The element of type <see cref="T"/> stored at the specified slot and Generation.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="slotIndex"/> is less than zero or greater than or equal to the capacity.</exception> /// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="slotIndex"/> is less than zero or greater than or equal to the capacity.</exception>
/// <exception cref="InvalidOperationException">Thrown when the specified slot is not occupied or the generation does not match.</exception> /// <exception cref="InvalidOperationException">Thrown when the specified slot is not occupied or the Generation does not match.</exception>
public readonly T GetElementAt(int slotIndex, int generation) public readonly T GetElementAt(int slotIndex, int generation)
{ {
if (!Contains(slotIndex, generation)) if (!Contains(slotIndex, generation))
@@ -288,13 +288,13 @@ public unsafe struct UnsafeSlotMap<T> : IUnsafeCollection<T>
} }
/// <summary> /// <summary>
/// Returns a reference to the element at the specified slot index and generation, if it exists; otherwise, returns /// Returns a reference to the element at the specified slot index and Generation, if it exists; otherwise, returns
/// a null reference. /// a null reference.
/// </summary> /// </summary>
/// <param name="slotIndex">The zero-based index of the slot to retrieve. Must be within the valid range of allocated slots.</param> /// <param name="slotIndex">The zero-based index of the slot to retrieve. Must be within the valid range of allocated slots.</param>
/// <param name="generation">The expected generation value for the slot. Used to verify that the slot has not been recycled or replaced.</param> /// <param name="generation">The expected Generation value for the slot. Used to verify that the slot has not been recycled or replaced.</param>
/// <param name="exist">When this method returns, contains <see langword="true"/> if a valid element exists at the specified slot and generation; otherwise, <see langword="false"/>.</param> /// <param name="exist">When this method returns, contains <see langword="true"/> if a valid element exists at the specified slot and Generation; otherwise, <see langword="false"/>.</param>
/// <returns>A reference to the element of type <typeparamref name="T"/> at the specified slot and generation if it exists; otherwise, a null reference.</returns> /// <returns>A reference to the element of type <typeparamref name="T"/> at the specified slot and Generation if it exists; otherwise, a null reference.</returns>
public ref T GetElementReferenceAt(int slotIndex, int generation, out bool exist) public ref T GetElementReferenceAt(int slotIndex, int generation, out bool exist)
{ {
if (!Contains(slotIndex, generation)) if (!Contains(slotIndex, generation))

View File

@@ -78,7 +78,7 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
private UnsafeArray<T> _dense; private UnsafeArray<T> _dense;
private UnsafeArray<int> _generations; private UnsafeArray<int> _generations;
private UnsafeArray<int> _sparse; private UnsafeArray<int> _sparse;
private UnsafeArray<int> _reverse; // Maps dense index to sparse index. Since this is a general purpose sparse set, we have to include reverse array. In real world ecs, this should be replaced with entity id array. private UnsafeArray<int> _reverse; // Maps dense index to sparse index. Since this is a general purpose sparse set, we have to include reverse array. In real world ecs, this should be replaced with entity ID array.
private UnsafeStack<int> _freeSparse; private UnsafeStack<int> _freeSparse;
private int _count; private int _count;
@@ -168,7 +168,7 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
/// Adds a value to the sparse set and returns a unique sparse index for the value. /// Adds a value to the sparse set and returns a unique sparse index for the value.
/// </summary> /// </summary>
/// <param name="value">The value to add to the sparse set.</param> /// <param name="value">The value to add to the sparse set.</param>
/// <param name="generation">Outputs the generation number associated with the added value.</param> /// <param name="generation">Outputs the Generation number associated with the added value.</param>
/// <returns>A unique sparse index that can be used to reference this value.</returns> /// <returns>A unique sparse index that can be used to reference this value.</returns>
public int Add(T value, out int generation) public int Add(T value, out int generation)
{ {
@@ -205,7 +205,7 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
/// Removes the value at the specified sparse index. /// Removes the value at the specified sparse index.
/// </summary> /// </summary>
/// <param name="sparseIndex">The sparse index of the value to remove.</param> /// <param name="sparseIndex">The sparse index of the value to remove.</param>
/// <param name="generation">The generation number associated with the sparse index to validate.</param> /// <param name="generation">The Generation number associated with the sparse index to validate.</param>
/// <param name="item">When this method returns, contains the item that was removed if the removal was successful; otherwise, the default value for type <typeparamref name="T"/>.</param> /// <param name="item">When this method returns, contains the item that was removed if the removal was successful; otherwise, the default value for type <typeparamref name="T"/>.</param>
/// <returns>True if the value was removed, false if the sparse index was not found.</returns> /// <returns>True if the value was removed, false if the sparse index was not found.</returns>
public bool Remove(int sparseIndex, int generation, out T item) public bool Remove(int sparseIndex, int generation, out T item)
@@ -234,7 +234,7 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
// Mark the sparse index as unused and add to free list // Mark the sparse index as unused and add to free list
_sparse[sparseIndex] = -1; _sparse[sparseIndex] = -1;
_generations[sparseIndex]++; // Increment generation to invalidate old references _generations[sparseIndex]++; // Increment Generation to invalidate old references
item = _dense[denseIndex]; item = _dense[denseIndex];
@@ -248,7 +248,7 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
/// Removes the value at the specified sparse index. /// Removes the value at the specified sparse index.
/// </summary> /// </summary>
/// <param name="sparseIndex">The sparse index of the value to remove.</param> /// <param name="sparseIndex">The sparse index of the value to remove.</param>
/// <param name="generation">The generation number associated with the sparse index to validate.</param> /// <param name="generation">The Generation number associated with the sparse index to validate.</param>
/// <returns>True if the value was removed, false if the sparse index was not found.</returns> /// <returns>True if the value was removed, false if the sparse index was not found.</returns>
public bool Remove(int sparseIndex, int generation) public bool Remove(int sparseIndex, int generation)
{ {
@@ -275,7 +275,7 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
// Mark the sparse index as unused and add to free list // Mark the sparse index as unused and add to free list
_sparse[sparseIndex] = -1; _sparse[sparseIndex] = -1;
_generations[sparseIndex]++; // Increment generation to invalidate old references _generations[sparseIndex]++; // Increment Generation to invalidate old references
_freeSparse.Push(sparseIndex); _freeSparse.Push(sparseIndex);
_count--; _count--;
@@ -287,7 +287,7 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
/// Checks if the sparse set contains a value at the specified sparse index. /// Checks if the sparse set contains a value at the specified sparse index.
/// </summary> /// </summary>
/// <param name="sparseIndex">The sparse index to check.</param> /// <param name="sparseIndex">The sparse index to check.</param>
/// <param name="generation">The generation number to validate against the stored generation.</param> /// <param name="generation">The Generation number to validate against the stored Generation.</param>
/// <returns>True if the sparse index is valid and contains a value, false otherwise.</returns> /// <returns>True if the sparse index is valid and contains a value, false otherwise.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool Contains(int sparseIndex, int generation) public readonly bool Contains(int sparseIndex, int generation)
@@ -302,10 +302,10 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
} }
/// <summary> /// <summary>
/// Gets the value at the specified sparse index and generation. /// Gets the value at the specified sparse index and Generation.
/// </summary> /// </summary>
/// <param name="sparseIndex">The sparse index to retrieve the value from.</param> /// <param name="sparseIndex">The sparse index to retrieve the value from.</param>
/// <param name="generation">The generation number to validate against the stored generation.</param> /// <param name="generation">The Generation number to validate against the stored Generation.</param>
/// <param name="value">When this method returns, contains the value at the specified sparse index, if found.</param> /// <param name="value">When this method returns, contains the value at the specified sparse index, if found.</param>
/// <returns>True if the sparse index contains a value, false otherwise.</returns> /// <returns>True if the sparse index contains a value, false otherwise.</returns>
public readonly bool TryGetValue(int sparseIndex, int generation, out T value) public readonly bool TryGetValue(int sparseIndex, int generation, out T value)
@@ -321,10 +321,10 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
} }
/// <summary> /// <summary>
/// Gets the value at the specified sparse index and generation. /// Gets the value at the specified sparse index and Generation.
/// </summary> /// </summary>
/// <param name="sparseIndex">The sparse index to retrieve the value from.</param> /// <param name="sparseIndex">The sparse index to retrieve the value from.</param>
/// <param name="generation">The generation number to validate against the stored generation.</param> /// <param name="generation">The Generation number to validate against the stored Generation.</param>
/// <returns>The value at the specified sparse index.</returns> /// <returns>The value at the specified sparse index.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the sparse index is not found.</exception> /// <exception cref="ArgumentOutOfRangeException">Thrown when the sparse index is not found.</exception>
public readonly T GetValue(int sparseIndex, int generation) public readonly T GetValue(int sparseIndex, int generation)
@@ -338,10 +338,10 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
} }
/// <summary> /// <summary>
/// Gets reference of the value at the specified sparse index and generation. /// Gets reference of the value at the specified sparse index and Generation.
/// </summary> /// </summary>
/// <param name="sparseIndex">The sparse index to retrieve the value from.</param> /// <param name="sparseIndex">The sparse index to retrieve the value from.</param>
/// <param name="generation">The generation number to validate against the stored generation.</param> /// <param name="generation">The Generation number to validate against the stored Generation.</param>
/// <param name="exist">Outputs whether the sparse index exists in the set.</param> /// <param name="exist">Outputs whether the sparse index exists in the set.</param>
/// <returns>Reference of the value at the specified sparse index.</returns> /// <returns>Reference of the value at the specified sparse index.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the sparse index is not found.</exception> /// <exception cref="ArgumentOutOfRangeException">Thrown when the sparse index is not found.</exception>
@@ -361,7 +361,7 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
/// Updates the value at the specified sparse index. /// Updates the value at the specified sparse index.
/// </summary> /// </summary>
/// <param name="sparseIndex">The sparse index of the value to update.</param> /// <param name="sparseIndex">The sparse index of the value to update.</param>
/// <param name="generation">The generation number to validate against the stored generation.</param> /// <param name="generation">The Generation number to validate against the stored Generation.</param>
/// <param name="value">The new value.</param> /// <param name="value">The new value.</param>
/// <returns>True if the value was updated, false if the sparse index was not found.</returns> /// <returns>True if the value was updated, false if the sparse index was not found.</returns>
public bool SetValue(int sparseIndex, int generation, T value) public bool SetValue(int sparseIndex, int generation, T value)

View File

@@ -23,7 +23,7 @@ internal class UnsafeStackDebugView<T>
{ {
var items = new T[_stack.Count]; var items = new T[_stack.Count];
var pItems = (T*)_stack.GetUnsafePtr(); var pItems = (T*)_stack.GetUnsafePtr();
for (int i = 0; i < _stack.Count; i++) for (var i = 0; i < _stack.Count; i++)
{ {
items[i] = pItems[i]; items[i] = pItems[i];
} }

View File

@@ -1,6 +1,5 @@
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text; using System.Text;
namespace Misaki.HighPerformance.LowLevel; namespace Misaki.HighPerformance.LowLevel;

View File

@@ -17,6 +17,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible> <IsAotCompatible>True</IsAotCompatible>
<DefineConstants>$(DefineConstants);ENABLE_SAFETY_CHECKS</DefineConstants>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
@@ -36,6 +37,10 @@
</Content> </Content>
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="TerraFX.Interop.Mimalloc" Version="1.6.7.2" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<None Update="Collections\FixedString.tt"> <None Update="Collections\FixedString.tt">
<Generator>TextTemplatingFileGenerator</Generator> <Generator>TextTemplatingFileGenerator</Generator>

View File

@@ -41,10 +41,31 @@ This package is the lowest-level layer in the solution. It is intended for code
## Example ## Example
```csharp ```csharp
// The low-level layer is meant for advanced ownership and allocation scenarios. var opts = new AllocationManagerInitOpts
// Prefer the higher-level packages when they already satisfy your use case. {
ArenaCapacity = 1024 * 1024,
StackCapacity = 1024 * 1024,
FreeListConcurrencyLevel = 1
};
AllocationManager.Initialize(opts);
var arr = new UnsafeArray<int>(10, Allocator.Persistent);
// Use the array
arr.Dispose();
AllocationManager.Dispose();
``` ```
You can enable debug features for leak detection and use-after-free checks by defining `ENABLE_SAFETY_CHECKS` in your project. And define `ENABLE_DEBUG_LAYER` to enable additional debug features such as tracking allocations and providing detailed error messages.
> Which means if you disable the safety checks, the library will not perform any safety checks and provide the maximum performance, and it will be your responsibility to ensure correct usage to avoid memory leaks and undefined behavior.
> Even `IUnsafeCollection.IsCreated` will only check if the internal pointer is non-null, without verifying the actual validity of the memory.
You can also define `ENABLE_MIMALLOC` to use mimalloc as the underlying allocator instead of the default C allocator.
> Using mimalloc requires to install the TerraFX.Interop.Mimalloc package and ensure the native mimalloc library is available at runtime.
## Package reference ## Package reference
```bash ```bash

View File

@@ -1,6 +1,9 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning; using System.Runtime.Versioning;
#if ENABLE_MIMALLOC
using TerraFX.Interop.Mimalloc;
#endif
namespace Misaki.HighPerformance.LowLevel.Utilities; namespace Misaki.HighPerformance.LowLevel.Utilities;
@@ -66,7 +69,9 @@ public static unsafe partial class MemoryUtility
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* Malloc(nuint size) public static void* Malloc(nuint size)
{ {
#if NET6_0_OR_GREATER #if ENABLE_MIMALLOC
return Mimalloc.mi_malloc(size);
#elif NET6_0_OR_GREATER
return NativeMemory.Alloc(size); return NativeMemory.Alloc(size);
#else #else
return Marshal.AllocHGlobal((IntPtr)size).ToPointer(); return Marshal.AllocHGlobal((IntPtr)size).ToPointer();
@@ -81,7 +86,9 @@ public static unsafe partial class MemoryUtility
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* Calloc(nuint size) public static void* Calloc(nuint size)
{ {
#if NET6_0_OR_GREATER #if ENABLE_MIMALLOC
return Mimalloc.mi_zalloc(size);
#elif NET6_0_OR_GREATER
return NativeMemory.AllocZeroed(size); return NativeMemory.AllocZeroed(size);
#else #else
var ptr = Marshal.AllocHGlobal((IntPtr)size).ToPointer(); var ptr = Marshal.AllocHGlobal((IntPtr)size).ToPointer();
@@ -99,7 +106,9 @@ public static unsafe partial class MemoryUtility
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* AlignedAlloc(nuint size, nuint alignment) public static void* AlignedAlloc(nuint size, nuint alignment)
{ {
#if NET6_0_OR_GREATER #if ENABLE_MIMALLOC
return Mimalloc.mi_aligned_alloc(alignment, size);
#elif NET6_0_OR_GREATER
return NativeMemory.AlignedAlloc(size, alignment); return NativeMemory.AlignedAlloc(size, alignment);
#else #else
return Marshal.AllocHGlobal((IntPtr)(size + alignment - 1)).ToPointer(); return Marshal.AllocHGlobal((IntPtr)(size + alignment - 1)).ToPointer();
@@ -115,7 +124,9 @@ public static unsafe partial class MemoryUtility
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* Realloc(void* ptr, nuint size) public static void* Realloc(void* ptr, nuint size)
{ {
#if NET6_0_OR_GREATER #if ENABLE_MIMALLOC
return Mimalloc.mi_realloc(ptr, size);
#elif NET6_0_OR_GREATER
return NativeMemory.Realloc(ptr, size); return NativeMemory.Realloc(ptr, size);
#else #else
return Marshal.ReAllocHGlobal((IntPtr)ptr, (IntPtr)size).ToPointer(); return Marshal.ReAllocHGlobal((IntPtr)ptr, (IntPtr)size).ToPointer();
@@ -133,7 +144,9 @@ public static unsafe partial class MemoryUtility
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* AlignedRealloc(void* ptr, nuint size, nuint alignment) public static void* AlignedRealloc(void* ptr, nuint size, nuint alignment)
{ {
#if NET6_0_OR_GREATER #if ENABLE_MIMALLOC
return Mimalloc.mi_realloc_aligned(ptr, size, alignment);
#elif NET6_0_OR_GREATER
return NativeMemory.AlignedRealloc(ptr, size, alignment); return NativeMemory.AlignedRealloc(ptr, size, alignment);
#else #else
var newPtr = Marshal.AllocHGlobal((IntPtr)(size + alignment - 1)).ToPointer(); var newPtr = Marshal.AllocHGlobal((IntPtr)(size + alignment - 1)).ToPointer();
@@ -159,7 +172,9 @@ 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 NET6_0_OR_GREATER #if ENABLE_MIMALLOC
Mimalloc.mi_free(ptr);
#elif NET6_0_OR_GREATER
NativeMemory.Free(ptr); NativeMemory.Free(ptr);
#else #else
Marshal.FreeHGlobal((IntPtr)ptr); Marshal.FreeHGlobal((IntPtr)ptr);
@@ -174,7 +189,9 @@ 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 NET6_0_OR_GREATER #if ENABLE_MIMALLOC
Mimalloc.mi_free(ptr);
#elif NET6_0_OR_GREATER
NativeMemory.AlignedFree(ptr); NativeMemory.AlignedFree(ptr);
#else #else
Marshal.FreeHGlobal((IntPtr)ptr); Marshal.FreeHGlobal((IntPtr)ptr);

View File

@@ -1,6 +1,5 @@
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel.Utilities; namespace Misaki.HighPerformance.LowLevel.Utilities;

View File

@@ -1,7 +1,3 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Misaki.HighPerformance.Mathematics; namespace Misaki.HighPerformance.Mathematics;
internal class AutoSIMDAttribute internal class AutoSIMDAttribute

File diff suppressed because it is too large Load Diff

View File

@@ -8606,7 +8606,7 @@ public static partial class math
var hash = seed + Prime5; var hash = seed + Prime5;
if (numBytes >= 16) if (numBytes >= 16)
{ {
uint4 state = new uint4(Prime1 + Prime2, Prime2, 0, (uint)-Prime1) + seed; var state = new uint4(Prime1 + Prime2, Prime2, 0, (uint)-Prime1) + seed;
var count = numBytes >> 4; var count = numBytes >> 4;
for (var i = 0; i < count; ++i) for (var i = 0; i < count; ++i)
@@ -8659,7 +8659,7 @@ public static partial class math
var hash = seed + Prime5; var hash = seed + Prime5;
if (numBytes >= 16) if (numBytes >= 16)
{ {
uint4 state = new uint4(Prime1 + Prime2, Prime2, 0, (uint)-Prime1) + seed; var state = new uint4(Prime1 + Prime2, Prime2, 0, (uint)-Prime1) + seed;
var count = numBytes >> 4; var count = numBytes >> 4;
for (var i = 0; i < count; ++i) for (var i = 0; i < count; ++i)

View File

@@ -67,7 +67,7 @@ internal unsafe struct NoiseJobVector : IJobParallel
public void Execute(int startIndex, int endIndex, ref readonly JobExecutionContext ctx) public void Execute(int startIndex, int endIndex, ref readonly JobExecutionContext ctx)
{ {
for (int i = startIndex; i < endIndex; i++) for (var i = startIndex; i < endIndex; i++)
{ {
var x = i % width; var x = i % width;
var y = i / height; var y = i / height;
@@ -201,7 +201,7 @@ internal unsafe struct NoiseJobMathV : IJobParallel
public void Execute(int startIndex, int endIndex, ref readonly JobExecutionContext ctx) public void Execute(int startIndex, int endIndex, ref readonly JobExecutionContext ctx)
{ {
for (int i = startIndex; i < endIndex; i++) for (var i = startIndex; i < endIndex; i++)
{ {
var baseIndex = i * 8; var baseIndex = i * 8;

View File

@@ -9,7 +9,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>$(DefineConstants);ENABLE_COLLECTION_CHECKS;PLATFORM_WINDOWS</DefineConstants> <DefineConstants>$(DefineConstants);PLATFORM_WINDOWS;ENABLE_SAFETY_CHECKS</DefineConstants>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">

View File

@@ -1,6 +1,5 @@
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
//BenchmarkRunner.Run<SPMDBenchmark>(); //BenchmarkRunner.Run<SPMDBenchmark>();
//var hashMap = new UnsafeHashMap<int, int>(10, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent); //var hashMap = new UnsafeHashMap<int, int>(10, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);

View File

@@ -1,5 +1,4 @@
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using System.Diagnostics;
namespace Misaki.HighPerformance.Test.UnitTest.Buffer; namespace Misaki.HighPerformance.Test.UnitTest.Buffer;
@@ -12,9 +11,9 @@ public unsafe class TestFreeList
using var freeList = new FreeList(8, 1024); using var freeList = new FreeList(8, 1024);
// Allocate various sizes // Allocate various sizes
void* p1 = freeList.Allocate(16, 8); var p1 = freeList.Allocate(16, 8);
void* p2 = freeList.Allocate(32, 8); var p2 = freeList.Allocate(32, 8);
void* p3 = freeList.Allocate(64, 8); var p3 = freeList.Allocate(64, 8);
Assert.IsTrue(p1 != null); Assert.IsTrue(p1 != null);
Assert.IsTrue(p2 != null); Assert.IsTrue(p2 != null);
@@ -26,8 +25,8 @@ public unsafe class TestFreeList
freeList.Free(p3); freeList.Free(p3);
// Allocate again - should reuse from buckets (or at least succeed) // Allocate again - should reuse from buckets (or at least succeed)
void* p4 = freeList.Allocate(16, 8); var p4 = freeList.Allocate(16, 8);
void* p5 = freeList.Allocate(32, 8); var p5 = freeList.Allocate(32, 8);
Assert.IsTrue(p4 != null); Assert.IsTrue(p4 != null);
Assert.IsTrue(p5 != null); Assert.IsTrue(p5 != null);
@@ -44,21 +43,23 @@ public unsafe class TestFreeList
using var freeList = new FreeList(8, 64 * 1024, threadCount); using var freeList = new FreeList(8, 64 * 1024, threadCount);
var threads = new Thread[threadCount]; var threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) for (var i = 0; i < threadCount; i++)
{ {
threads[i] = new Thread(() => threads[i] = new Thread(() =>
{ {
for (int j = 0; j < iterations; j++) for (var j = 0; j < iterations; j++)
{ {
void* ptr = freeList.Allocate(16, 8); var ptr = freeList.Allocate(16, 8);
Assert.IsTrue(ptr != null); Assert.IsTrue(ptr != null);
freeList.Free(ptr); freeList.Free(ptr);
} }
}); });
} }
foreach (var t in threads) t.Start(); foreach (var t in threads)
foreach (var t in threads) t.Join(); t.Start();
foreach (var t in threads)
t.Join();
} }
[TestMethod] [TestMethod]
@@ -73,22 +74,22 @@ public unsafe class TestFreeList
var producers = new Thread[producerCount]; var producers = new Thread[producerCount];
var consumers = new Thread[consumerCount]; var consumers = new Thread[consumerCount];
bool producing = true; var producing = true;
for (int i = 0; i < producerCount; i++) for (var i = 0; i < producerCount; i++)
{ {
producers[i] = new Thread(() => producers[i] = new Thread(() =>
{ {
for (int j = 0; j < iterations; j++) for (var j = 0; j < iterations; j++)
{ {
void* ptr = freeList.Allocate(32, 8); var ptr = freeList.Allocate(32, 8);
Assert.IsTrue(ptr != null); Assert.IsTrue(ptr != null);
queue.Enqueue((IntPtr)ptr); queue.Enqueue((IntPtr)ptr);
} }
}); });
} }
for (int i = 0; i < consumerCount; i++) for (var i = 0; i < consumerCount; i++)
{ {
consumers[i] = new Thread(() => consumers[i] = new Thread(() =>
{ {
@@ -106,12 +107,16 @@ public unsafe class TestFreeList
}); });
} }
foreach (var t in producers) t.Start(); foreach (var t in producers)
foreach (var t in consumers) t.Start(); t.Start();
foreach (var t in consumers)
t.Start();
foreach (var t in producers) t.Join(); foreach (var t in producers)
t.Join();
Volatile.Write(ref producing, false); Volatile.Write(ref producing, false);
foreach (var t in consumers) t.Join(); foreach (var t in consumers)
t.Join();
} }
[TestMethod] [TestMethod]
@@ -122,18 +127,20 @@ public unsafe class TestFreeList
using var freeList = new FreeList(8, 1024, 1); using var freeList = new FreeList(8, 1024, 1);
var threads = new Thread[threadCount]; var threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) for (var i = 0; i < threadCount; i++)
{ {
threads[i] = new Thread(() => threads[i] = new Thread(() =>
{ {
void* ptr = freeList.Allocate(16, 8); var ptr = freeList.Allocate(16, 8);
Assert.IsTrue(ptr != null); Assert.IsTrue(ptr != null);
freeList.Free(ptr); freeList.Free(ptr);
}); });
} }
foreach (var t in threads) t.Start(); foreach (var t in threads)
foreach (var t in threads) t.Join(); t.Start();
foreach (var t in threads)
t.Join();
} }
[TestMethod] [TestMethod]
@@ -143,7 +150,7 @@ public unsafe class TestFreeList
// Allocate larger than default chunk size // Allocate larger than default chunk size
nuint largeSize = 2048; nuint largeSize = 2048;
void* ptr = freeList.Allocate(largeSize, 8); var ptr = freeList.Allocate(largeSize, 8);
Assert.IsTrue(ptr != null); Assert.IsTrue(ptr != null);
freeList.Free(ptr); freeList.Free(ptr);

View File

@@ -49,7 +49,7 @@ public class TestConcurrentSlotMap
{ {
var slotIndex = _slotMap.Add(200, out var generation); var slotIndex = _slotMap.Add(200, out var generation);
Assert.IsTrue(_slotMap.Contains(slotIndex, generation)); Assert.IsTrue(_slotMap.Contains(slotIndex, generation));
var removed = _slotMap.Remove(slotIndex, generation + 1); // Wrong generation var removed = _slotMap.Remove(slotIndex, generation + 1); // Wrong Generation
Assert.IsFalse(removed); Assert.IsFalse(removed);
Assert.IsTrue(_slotMap.Contains(slotIndex, generation)); Assert.IsTrue(_slotMap.Contains(slotIndex, generation));
} }
@@ -71,11 +71,11 @@ public class TestConcurrentSlotMap
const int itemsPerThread = 1000; const int itemsPerThread = 1000;
var tasks = new List<Task>(); var tasks = new List<Task>();
for (int t = 0; t < threadCount; t++) for (var t = 0; t < threadCount; t++)
{ {
tasks.Add(Task.Run(() => tasks.Add(Task.Run(() =>
{ {
for (int i = 0; i < itemsPerThread; i++) for (var i = 0; i < itemsPerThread; i++)
{ {
_slotMap.Add(i, out _); _slotMap.Add(i, out _);
} }
@@ -98,11 +98,11 @@ public class TestConcurrentSlotMap
var count = 0; var count = 0;
for (int t = 0; t < threadCount; t++) for (var t = 0; t < threadCount; t++)
{ {
tasks.Add(Task.Run(() => tasks.Add(Task.Run(() =>
{ {
for (int i = 0; i < operationsPerThread; i++) for (var i = 0; i < operationsPerThread; i++)
{ {
if (rand.NextDouble() < 0.5) if (rand.NextDouble() < 0.5)
{ {

View File

@@ -1,5 +1,4 @@
using Misaki.HighPerformance.Collections; using Misaki.HighPerformance.Collections;
using System.Collections.Concurrent;
namespace Misaki.HighPerformance.Test.UnitTest.Collections; namespace Misaki.HighPerformance.Test.UnitTest.Collections;
@@ -43,7 +42,7 @@ public class TestSlotMap
{ {
var slotIndex = _slotMap.Add(200, out var generation); var slotIndex = _slotMap.Add(200, out var generation);
Assert.IsTrue(_slotMap.Contains(slotIndex, generation)); Assert.IsTrue(_slotMap.Contains(slotIndex, generation));
var removed = _slotMap.Remove(slotIndex, generation + 1); // Wrong generation var removed = _slotMap.Remove(slotIndex, generation + 1); // Wrong Generation
Assert.IsFalse(removed); Assert.IsFalse(removed);
Assert.IsTrue(_slotMap.Contains(slotIndex, generation)); Assert.IsTrue(_slotMap.Contains(slotIndex, generation));
} }

View File

@@ -29,12 +29,12 @@ public class TestUnsafeArray
[TestMethod] [TestMethod]
public void TestIndexAccess() public void TestIndexAccess()
{ {
for (int i = 0; i < _arr.Count; i++) for (var i = 0; i < _arr.Count; i++)
{ {
_arr[i] = i * 10; _arr[i] = i * 10;
} }
for (int i = 0; i < _arr.Count; i++) for (var i = 0; i < _arr.Count; i++)
{ {
Assert.AreEqual(i * 10, _arr[i]); Assert.AreEqual(i * 10, _arr[i]);
} }
@@ -45,7 +45,7 @@ public class TestUnsafeArray
{ {
_arr.Clear(); _arr.Clear();
int expectedValue = 0; var expectedValue = 0;
foreach (var item in _arr) foreach (var item in _arr)
{ {
Assert.AreEqual(expectedValue, item); Assert.AreEqual(expectedValue, item);

View File

@@ -23,14 +23,14 @@ public class TestUnsafeStack
[TestMethod] [TestMethod]
public void TestPushPop() public void TestPushPop()
{ {
for (int i = 0; i < 10; i++) for (var i = 0; i < 10; i++)
{ {
_stack.Push(i); _stack.Push(i);
} }
Assert.AreEqual(10, _stack.Count); Assert.AreEqual(10, _stack.Count);
for (int i = 9; i >= 0; i--) for (var i = 9; i >= 0; i--)
{ {
int value = _stack.Pop(); var value = _stack.Pop();
Assert.AreEqual(i, value); Assert.AreEqual(i, value);
} }
Assert.AreEqual(0, _stack.Count); Assert.AreEqual(0, _stack.Count);
@@ -40,7 +40,7 @@ public class TestUnsafeStack
public void TestPeek() public void TestPeek()
{ {
_stack.Push(42); _stack.Push(42);
int value = _stack.Peek(); var value = _stack.Peek();
Assert.AreEqual(42, value); Assert.AreEqual(42, value);
Assert.AreEqual(1, _stack.Count); Assert.AreEqual(1, _stack.Count);
} }
@@ -48,12 +48,12 @@ public class TestUnsafeStack
[TestMethod] [TestMethod]
public void TestEnumeration() public void TestEnumeration()
{ {
for (int i = 0; i < 5; i++) for (var i = 0; i < 5; i++)
{ {
_stack.Push(i); _stack.Push(i);
} }
int expected = 4; var expected = 4;
foreach (var item in _stack) foreach (var item in _stack)
{ {
Assert.AreEqual(expected, item); Assert.AreEqual(expected, item);

View File

@@ -36,7 +36,7 @@ public static class CompressStoreTest
); );
} }
private unsafe static void TestPattern_Double(double[] input, bool[] keepPattern) private static unsafe void TestPattern_Double(double[] input, bool[] keepPattern)
{ {
// 1. Setup Input Vector // 1. Setup Input Vector
// Handle case where Vector<TLane> is smaller than 8 (e.g. 2 or 4) // Handle case where Vector<TLane> is smaller than 8 (e.g. 2 or 4)

View File

@@ -4,7 +4,6 @@ using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities; using Misaki.HighPerformance.LowLevel.Utilities;
using Misaki.HighPerformance.Mathematics.SPMD; using Misaki.HighPerformance.Mathematics.SPMD;
using Misaki.HighPerformance.Test.Jobs; using Misaki.HighPerformance.Test.Jobs;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Test.UnitTest.Jobs; namespace Misaki.HighPerformance.Test.UnitTest.Jobs;