Merge pull request 'develop' (#5) from develop into main
Reviewed-on: #5
This commit was merged in pull request #5.
This commit is contained in:
@@ -1,4 +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, void*>;
|
|
||||||
global using unsafe FreeFunc = delegate*<void*, void*, void>;
|
global using unsafe AllocFunc = delegate*<void*, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle*, void*>;
|
||||||
global using unsafe ReallocFunc = delegate*<void*, void*, nuint, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption, void*>;
|
global using unsafe ReallocFunc = delegate*<void*, void*, nuint, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle*, void*>;
|
||||||
|
global using unsafe FreeFunc = delegate*<void*, void*, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle, void>;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Misaki.HighPerformance.Collections;
|
||||||
using Misaki.HighPerformance.LowLevel.Contracts;
|
using Misaki.HighPerformance.LowLevel.Contracts;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
@@ -5,6 +6,22 @@ using System.Runtime.InteropServices;
|
|||||||
|
|
||||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
|
||||||
|
public readonly struct MemoryHandle
|
||||||
|
{
|
||||||
|
public readonly int id;
|
||||||
|
public readonly int generation;
|
||||||
|
|
||||||
|
public readonly bool IsValid => AllocationManager.ContainsAllocation(this);
|
||||||
|
|
||||||
|
public readonly static MemoryHandle Invalid = new(-1, -1);
|
||||||
|
|
||||||
|
public MemoryHandle(int id, int generation)
|
||||||
|
{
|
||||||
|
this.id = id;
|
||||||
|
this.generation = generation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Holds information about a memory allocation.
|
/// Holds information about a memory allocation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -44,7 +61,7 @@ public static unsafe class AllocationManager
|
|||||||
public nint stackHandle; // GCHandle to managed StackTrace (stored as IntPtr)
|
public nint stackHandle; // GCHandle to managed StackTrace (stored as IntPtr)
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe struct ArenaAllocator : IAllocator, IDisposable
|
private struct ArenaAllocator : IAllocator, IDisposable
|
||||||
{
|
{
|
||||||
private DynamicArena _arena;
|
private DynamicArena _arena;
|
||||||
private AllocationHandle _handle;
|
private AllocationHandle _handle;
|
||||||
@@ -53,39 +70,49 @@ public static unsafe class AllocationManager
|
|||||||
|
|
||||||
public void Init(uint initialSize)
|
public void Init(uint initialSize)
|
||||||
{
|
{
|
||||||
_arena = new(initialSize);
|
_arena = new DynamicArena(initialSize);
|
||||||
_handle = new(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
|
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &Free);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
|
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
||||||
{
|
{
|
||||||
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)
|
||||||
|
{
|
||||||
|
*pHandle = MemoryHandle.Invalid;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
*pHandle = AddAllocation((IntPtr)ptr);
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
|
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
||||||
{
|
{
|
||||||
var selfPtr = (ArenaAllocator*)instance;
|
if (ptr == null)
|
||||||
var newPtr = selfPtr->_arena.Allocate(newSize, alignment, allocationOption);
|
|
||||||
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
|
||||||
|
|
||||||
if (allocationOption.HasFlag(AllocationOption.Clear))
|
|
||||||
{
|
{
|
||||||
if (newSize > oldSize)
|
return Allocate(instance, newSize, alignment, allocationOption, pHandle);
|
||||||
{
|
|
||||||
MemClear((byte*)newPtr + oldSize, newSize - oldSize);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We do not free the old pointer here, as it is managed by the arena.
|
var selfPtr = (ArenaAllocator*)instance;
|
||||||
|
var newPtr = selfPtr->_arena.Allocate(newSize, alignment, allocationOption);
|
||||||
|
if (newPtr == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
||||||
|
RemoveAllocation(*pHandle);
|
||||||
|
|
||||||
|
*pHandle = AddAllocation((IntPtr)newPtr);
|
||||||
return newPtr;
|
return newPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void FreeBlock(void* instance, void* ptr)
|
private static void Free(void* instance, void* ptr, MemoryHandle pHandle)
|
||||||
{
|
{
|
||||||
// The arena allocator does not free individual blocks, as it manages memory in chunks.
|
// The arena allocator does not free individual blocks, as it manages memory in chunks.
|
||||||
|
s_allocations.Remove(pHandle.id, pHandle.generation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
@@ -99,7 +126,7 @@ public static unsafe class AllocationManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe struct HeapAllocator : IAllocator
|
private struct HeapAllocator : IAllocator
|
||||||
{
|
{
|
||||||
private AllocationHandle _handle;
|
private AllocationHandle _handle;
|
||||||
|
|
||||||
@@ -107,27 +134,45 @@ public static unsafe class AllocationManager
|
|||||||
|
|
||||||
public void Init()
|
public void Init()
|
||||||
{
|
{
|
||||||
_handle = new(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
|
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &Free);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
|
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
||||||
{
|
{
|
||||||
return HeapAlloc(size, alignment, allocationOption);
|
return HeapAlloc(size, alignment, allocationOption, pHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
|
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
||||||
{
|
{
|
||||||
return HeapRealloc(ptr, oldSize, newSize, alignment, allocationOption);
|
if (ptr == null)
|
||||||
|
{
|
||||||
|
return Allocate(instance, newSize, alignment, allocationOption, pHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryHandle newHandle;
|
||||||
|
var newPtr = HeapAlloc(newSize, alignment, allocationOption, &newHandle);
|
||||||
|
if (newPtr == null)
|
||||||
|
{
|
||||||
|
// Allocation failed, return original pointer
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
||||||
|
HeapFree(ptr, *pHandle);
|
||||||
|
|
||||||
|
*pHandle = newHandle;
|
||||||
|
return newPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void FreeBlock(void* instance, void* ptr)
|
private static void Free(void* instance, void* ptr, MemoryHandle handle)
|
||||||
{
|
{
|
||||||
HeapFree(ptr);
|
HeapFree(ptr, handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe struct StackAllocator : IAllocator
|
private struct StackAllocator : IAllocator
|
||||||
{
|
{
|
||||||
|
// Thread-local stack for allocations. We does not track allocations across threads, which leads us to let system clean up the memory when thread exits.
|
||||||
[ThreadStatic]
|
[ThreadStatic]
|
||||||
private static Stack s_stack;
|
private static Stack s_stack;
|
||||||
private AllocationHandle _handle;
|
private AllocationHandle _handle;
|
||||||
@@ -139,33 +184,42 @@ public static unsafe class AllocationManager
|
|||||||
_handle = new(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
|
_handle = new(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
|
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
||||||
{
|
{
|
||||||
var ptr = s_stack.Allocate(size, alignment, allocationOption);
|
var ptr = s_stack.Allocate(size, alignment, allocationOption);
|
||||||
|
if (ptr == null)
|
||||||
|
{
|
||||||
|
*pHandle = MemoryHandle.Invalid;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
*pHandle = AddAllocation((IntPtr)ptr);
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
|
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
||||||
{
|
{
|
||||||
var newPtr = s_stack.Allocate(newSize, alignment, AllocationOption.None);
|
if (ptr == null)
|
||||||
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
|
||||||
|
|
||||||
if (allocationOption.HasFlag(AllocationOption.Clear))
|
|
||||||
{
|
{
|
||||||
if (newSize > oldSize)
|
return Allocate(instance, newSize, alignment, allocationOption, pHandle);
|
||||||
{
|
|
||||||
MemClear((byte*)newPtr + oldSize, newSize - oldSize);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We do not free the old pointer here, as it is managed by the stack.
|
var newPtr = s_stack.Allocate(newSize, alignment, allocationOption);
|
||||||
|
if (newPtr == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
||||||
|
RemoveAllocation(*pHandle);
|
||||||
|
|
||||||
|
*pHandle = AddAllocation((IntPtr)newPtr);
|
||||||
return newPtr;
|
return newPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void FreeBlock(void* instance, void* ptr)
|
private static void FreeBlock(void* instance, void* ptr, MemoryHandle pHandle)
|
||||||
{
|
{
|
||||||
// The stack allocator does not free individual blocks, as it manages memory in a stack-like manner.
|
s_allocations.Remove(pHandle.id, pHandle.generation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Stack.Scope CreateScope()
|
public static Stack.Scope CreateScope()
|
||||||
@@ -186,8 +240,12 @@ public static unsafe class AllocationManager
|
|||||||
private static AllocationHeader* s_liveHead;
|
private static AllocationHeader* s_liveHead;
|
||||||
private static SpinLock s_liveLock;
|
private static SpinLock s_liveLock;
|
||||||
|
|
||||||
// Lightweight allocation counter for non-debug layer (no sizes, just count of live heap blocks)
|
private readonly static ConcurrentSlotMap<IntPtr> s_allocations;
|
||||||
private static long s_activeHeapAllocations;
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of live persistent heap allocations when the debug layer is disabled.
|
||||||
|
/// </summary>
|
||||||
|
public static int LiveAllocationCount => s_allocations.Count;
|
||||||
|
|
||||||
static AllocationManager()
|
static AllocationManager()
|
||||||
{
|
{
|
||||||
@@ -197,6 +255,8 @@ public static unsafe class AllocationManager
|
|||||||
|
|
||||||
s_liveLock = new SpinLock(false);
|
s_liveLock = new SpinLock(false);
|
||||||
|
|
||||||
|
s_allocations = new ConcurrentSlotMap<IntPtr>(256);
|
||||||
|
|
||||||
s_pArenaAllocator->Init(_DEFAULT_MEMORY_POOL_SIZE);
|
s_pArenaAllocator->Init(_DEFAULT_MEMORY_POOL_SIZE);
|
||||||
s_pHeapAllocator->Init();
|
s_pHeapAllocator->Init();
|
||||||
s_pStackAllocator->Init();
|
s_pStackAllocator->Init();
|
||||||
@@ -260,12 +320,18 @@ public static unsafe class AllocationManager
|
|||||||
var next = header->next;
|
var next = header->next;
|
||||||
|
|
||||||
if (prev != null)
|
if (prev != null)
|
||||||
|
{
|
||||||
prev->next = next;
|
prev->next = next;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
s_liveHead = next;
|
s_liveHead = next;
|
||||||
|
}
|
||||||
|
|
||||||
if (next != null)
|
if (next != null)
|
||||||
|
{
|
||||||
next->prev = prev;
|
next->prev = prev;
|
||||||
|
}
|
||||||
|
|
||||||
header->prev = header->next = null;
|
header->prev = header->next = null;
|
||||||
}
|
}
|
||||||
@@ -336,7 +402,7 @@ public static unsafe class AllocationManager
|
|||||||
MemClear((byte*)newUser + oldSize, newSize - oldSize);
|
MemClear((byte*)newUser + oldSize, newSize - oldSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlink and free the old block (without freeing the StackTrace handle again)
|
// Unlink and free the old block (without freeing the StackTrace pHandle again)
|
||||||
oldHeader->stackHandle = 0;
|
oldHeader->stackHandle = 0;
|
||||||
UnlinkHeader(oldHeader);
|
UnlinkHeader(oldHeader);
|
||||||
AlignedFree(oldHeader->basePtr);
|
AlignedFree(oldHeader->basePtr);
|
||||||
@@ -344,11 +410,6 @@ public static unsafe class AllocationManager
|
|||||||
return newUser;
|
return newUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the number of live persistent heap allocations when the debug layer is disabled.
|
|
||||||
/// </summary>
|
|
||||||
public static long LiveHeapAllocationCount => Interlocked.Read(ref s_activeHeapAllocations);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables the debug layer, allowing additional diagnostic information to be collected.
|
/// Enables the debug layer, allowing additional diagnostic information to be collected.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -357,19 +418,19 @@ public static unsafe class AllocationManager
|
|||||||
{
|
{
|
||||||
// To avoid ambiguity between pointers allocated before/after enabling, this must be called
|
// To avoid ambiguity between pointers allocated before/after enabling, this must be called
|
||||||
// before any heap allocations are live.
|
// before any heap allocations are live.
|
||||||
if (Interlocked.Read(ref s_activeHeapAllocations) != 0)
|
if (s_allocations.Count != 0)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("EnableDebugLayer must be called before any heap allocations are active.");
|
throw new InvalidOperationException("EnableDebugLayer must be called before any allocations are active.");
|
||||||
}
|
}
|
||||||
|
|
||||||
s_debugLayer = true;
|
s_debugLayer = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a reference to the allocation handle for the specified allocator type.
|
/// Gets a reference to the allocation pHandle for the specified allocator type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="allocator">The allocator type for which to retrieve the allocation handle.</param>
|
/// <param name="allocator">The allocator type for which to retrieve the allocation pHandle.</param>
|
||||||
/// <returns>A reference to the allocation handle associated with the specified allocator type.</returns>
|
/// <returns>A reference to the allocation pHandle associated with the specified allocator type.</returns>
|
||||||
/// <exception cref="ArgumentException"></exception>
|
/// <exception cref="ArgumentException"></exception>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static ref AllocationHandle GetAllocationHandle(Allocator allocator)
|
public static ref AllocationHandle GetAllocationHandle(Allocator allocator)
|
||||||
@@ -397,7 +458,7 @@ public static unsafe class AllocationManager
|
|||||||
/// tracked. The default is <see cref="AllocationOption.None"/>.</param>
|
/// tracked. The default is <see cref="AllocationOption.None"/>.</param>
|
||||||
/// <returns>A pointer to the beginning of the allocated memory block.</returns>
|
/// <returns>A pointer to the beginning of the allocated memory block.</returns>
|
||||||
/// <exception cref="OutOfMemoryException">Thrown if the allocation fails.</exception>
|
/// <exception cref="OutOfMemoryException">Thrown if the allocation fails.</exception>
|
||||||
public static void* HeapAlloc(nuint size, nuint alignment, AllocationOption allocationOption = AllocationOption.None)
|
public static void* HeapAlloc(nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
||||||
{
|
{
|
||||||
void* ptr;
|
void* ptr;
|
||||||
if (s_debugLayer)
|
if (s_debugLayer)
|
||||||
@@ -409,50 +470,27 @@ public static unsafe class AllocationManager
|
|||||||
ptr = AlignedAlloc(size, alignment);
|
ptr = AlignedAlloc(size, alignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ptr == null)
|
||||||
|
{
|
||||||
|
*pHandle = MemoryHandle.Invalid;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (allocationOption.HasFlag(AllocationOption.Clear))
|
if (allocationOption.HasFlag(AllocationOption.Clear))
|
||||||
{
|
{
|
||||||
MemClear(ptr, size);
|
MemClear(ptr, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
Interlocked.Increment(ref s_activeHeapAllocations);
|
*pHandle = AddAllocation((IntPtr)ptr);
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reallocates a block of memory to a new size and alignment, optionally clearing newly allocated memory and
|
|
||||||
/// applying allocation options.
|
|
||||||
/// </summary>\
|
|
||||||
/// <param name="ptr">A pointer to the previously allocated memory block to be reallocated. Can be <see langword="null"/> to allocate new memory.</param>
|
|
||||||
/// <param name="oldSize">The size, in bytes, of the memory block currently pointed to by <paramref name="ptr"/>.</param>
|
|
||||||
/// <param name="newSize">The desired size, in bytes, for the reallocated memory block.</param>
|
|
||||||
/// <param name="alignment">The required alignment, in bytes, for the reallocated memory block. Must be a power of two.</param>
|
|
||||||
/// <param name="allocationOption">An optional set of flags that control allocation behavior, such as whether to clear newly allocated memory or
|
|
||||||
/// track the allocation. The default is <see cref="AllocationOption.None"/>.</param>
|
|
||||||
/// <returns>A pointer to the reallocated memory block with the specified size and alignment. Returns <see langword="null"/>
|
|
||||||
/// if the allocation fails.</returns>
|
|
||||||
public static void* HeapRealloc(void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption = AllocationOption.None)
|
|
||||||
{
|
|
||||||
if (s_debugLayer)
|
|
||||||
{
|
|
||||||
return DebugReallocate(ptr, oldSize, newSize, alignment, allocationOption);
|
|
||||||
}
|
|
||||||
|
|
||||||
var newPtr = AlignedRealloc(ptr, newSize, alignment);
|
|
||||||
if (allocationOption.HasFlag(AllocationOption.Clear)
|
|
||||||
&& newSize > oldSize)
|
|
||||||
{
|
|
||||||
MemClear((byte*)newPtr + oldSize, newSize - oldSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
return newPtr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Releases a block of unmanaged memory previously allocated by the heap allocator.
|
/// Releases a block of unmanaged memory previously allocated by the heap allocator.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ptr">A pointer to the memory block to be freed. The pointer must have been returned by a compatible heap allocation
|
/// <param name="ptr">A pointer to the memory block to be freed. The pointer must have been returned by a compatible heap allocation
|
||||||
/// method and must not be null.</param>
|
/// method and must not be null.</param>
|
||||||
public static void HeapFree(void* ptr)
|
public static void HeapFree(void* ptr, MemoryHandle handle)
|
||||||
{
|
{
|
||||||
if (s_debugLayer)
|
if (s_debugLayer)
|
||||||
{
|
{
|
||||||
@@ -463,7 +501,7 @@ public static unsafe class AllocationManager
|
|||||||
AlignedFree(ptr);
|
AlignedFree(ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
Interlocked.Decrement(ref s_activeHeapAllocations);
|
RemoveAllocation(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -485,6 +523,52 @@ public static unsafe class AllocationManager
|
|||||||
return StackAllocator.CreateScope();
|
return StackAllocator.CreateScope();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a memory allocation and returns a handle that can be used to manage or reference the allocated memory.
|
||||||
|
/// </summary>
|
||||||
|
/// <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>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static MemoryHandle AddAllocation(IntPtr ptr)
|
||||||
|
{
|
||||||
|
var id = s_allocations.Add(ptr, out var generation);
|
||||||
|
return new MemoryHandle(id, generation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the memory allocation associated with the specified handle.
|
||||||
|
/// </summary>
|
||||||
|
/// <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>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static bool RemoveAllocation(MemoryHandle handle)
|
||||||
|
{
|
||||||
|
return s_allocations.Remove(handle.id, handle.generation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to retrieve the memory allocation pointer associated with the specified handle.
|
||||||
|
/// </summary>
|
||||||
|
/// <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>
|
||||||
|
/// <returns>true if the allocation was found and <paramref name="ptr"/> contains a valid pointer; otherwise, false.</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static bool TryGetAllocation(MemoryHandle handle, out IntPtr ptr)
|
||||||
|
{
|
||||||
|
return s_allocations.TryGetElement(handle.id, handle.generation, out ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the specified memory handle refers to a currently tracked allocation.
|
||||||
|
/// </summary>
|
||||||
|
/// <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>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static bool ContainsAllocation(MemoryHandle handle)
|
||||||
|
{
|
||||||
|
return s_allocations.Contains(handle.id, handle.generation);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disposes of the AllocationManager, freeing all allocated memory and resources.
|
/// Disposes of the AllocationManager, freeing all allocated memory and resources.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -536,9 +620,10 @@ public static unsafe class AllocationManager
|
|||||||
throw new MemoryLeakException(CollectionsMarshal.AsSpan(snapshot));
|
throw new MemoryLeakException(CollectionsMarshal.AsSpan(snapshot));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (s_activeHeapAllocations != 0)
|
|
||||||
|
if (LiveAllocationCount != 0)
|
||||||
{
|
{
|
||||||
throw new MemoryLeakException($"Found {s_activeHeapAllocations} memory lakes! Please enable debug layer for more informations.");
|
throw new MemoryLeakException($"Found {LiveAllocationCount} memory lakes! Please enable debug layer for more informations.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s_pArenaAllocator != null)
|
if (s_pArenaAllocator != null)
|
||||||
@@ -557,7 +642,6 @@ public static unsafe class AllocationManager
|
|||||||
NativeMemory.Free(s_pStackAllocator);
|
NativeMemory.Free(s_pStackAllocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
s_activeHeapAllocations = 0;
|
|
||||||
s_disposed = true;
|
s_disposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
|
||||||
|
|
||||||
public unsafe struct SafeHandle
|
|
||||||
{
|
|
||||||
private const nuint _ALIGNMENT = 16u;
|
|
||||||
|
|
||||||
public int valid;
|
|
||||||
|
|
||||||
public static nuint GetAlignWithHeader(nuint baseAlign)
|
|
||||||
{
|
|
||||||
return Math.Max(_ALIGNMENT, baseAlign);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static nuint GetPaddedHeaderSize(nuint baseAlign)
|
|
||||||
{
|
|
||||||
var headerBaseSize = (nuint)sizeof(SafeHandle);
|
|
||||||
var dataAlignment = Math.Max(_ALIGNMENT, baseAlign);
|
|
||||||
return (headerBaseSize + (dataAlignment - 1u)) & ~(dataAlignment - 1u);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SafeHandle* GetSafeHandle(void* ptr, nuint baseAlign)
|
|
||||||
{
|
|
||||||
if (ptr == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var alignedHeaderSize = GetPaddedHeaderSize(baseAlign);
|
|
||||||
return (SafeHandle*)((byte*)ptr - alignedHeaderSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -82,11 +82,11 @@ public interface IUnTypedCollection : IUnsafeCollection
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The total size of the buffer in bytes.
|
/// The total size of the buffer in bytes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
uint Size
|
nuint Size
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
}
|
}
|
||||||
|
|
||||||
ref T GetElementAt<T>(uint index)
|
ref T GetElementAt<T>(nuint index)
|
||||||
where T : unmanaged;
|
where T : unmanaged;
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
|
||||||
using Misaki.HighPerformance.LowLevel.Contracts;
|
using Misaki.HighPerformance.LowLevel.Contracts;
|
||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
@@ -9,18 +9,16 @@ namespace Misaki.HighPerformance.LowLevel.Collections;
|
|||||||
public unsafe struct UnTypedArray : IUnTypedCollection
|
public unsafe struct UnTypedArray : IUnTypedCollection
|
||||||
{
|
{
|
||||||
private void* _buffer;
|
private void* _buffer;
|
||||||
private uint _size;
|
private nuint _size;
|
||||||
private uint _alignment;
|
private nuint _alignment;
|
||||||
|
|
||||||
private AllocationHandle* _handle;
|
private MemoryHandle _memoryHandle;
|
||||||
|
private AllocationHandle* _allocationHandle;
|
||||||
|
|
||||||
public readonly uint Size => _size;
|
public readonly nuint Size => _size;
|
||||||
public readonly uint Alignment => _alignment;
|
public readonly nuint Alignment => _alignment;
|
||||||
|
|
||||||
public readonly bool IsCreated
|
public readonly bool IsCreated => _buffer != null && _allocationHandle != null && _memoryHandle.IsValid;
|
||||||
{
|
|
||||||
get => _buffer != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs an UnsafeArray with a default size of 1 and uses the Persistent allocator.
|
/// Constructs an UnsafeArray with a default size of 1 and uses the Persistent allocator.
|
||||||
@@ -30,17 +28,20 @@ public unsafe struct UnTypedArray : IUnTypedCollection
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public UnTypedArray(uint size, uint alignment, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
|
public UnTypedArray(nuint size, nuint alignment, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
|
||||||
{
|
{
|
||||||
if (size <= 0)
|
if (size <= 0)
|
||||||
{
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(size), "Count must be greater than zero.");
|
throw new ArgumentOutOfRangeException(nameof(size), "Count must be greater than zero.");
|
||||||
}
|
}
|
||||||
|
|
||||||
_handle = (AllocationHandle*)Unsafe.AsPointer(ref handle);
|
MemoryHandle memHandle;
|
||||||
_buffer = handle.Alloc(_handle->Allocator, size, alignment, allocationOption);
|
_buffer = handle.Alloc(_allocationHandle->Allocator, size, alignment, allocationOption, &memHandle);
|
||||||
_size = size;
|
_size = size;
|
||||||
_alignment = alignment;
|
_alignment = alignment;
|
||||||
|
|
||||||
|
_memoryHandle = memHandle;
|
||||||
|
_allocationHandle = (AllocationHandle*)Unsafe.AsPointer(ref handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -50,7 +51,7 @@ public unsafe struct UnTypedArray : IUnTypedCollection
|
|||||||
/// <param name="allocator">Specifies the allocator to use for memory allocation, which determines the memory management strategy.</param>
|
/// <param name="allocator">Specifies the allocator to use for memory allocation, which determines the memory management strategy.</param>
|
||||||
/// <param name="allocationOption">Determines how the memory should be allocated.</param>
|
/// <param name="allocationOption">Determines how the memory should be allocated.</param>
|
||||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the specified number of elements is less than or equal to zero.</exception>
|
/// <exception cref="ArgumentOutOfRangeException">Thrown when the specified number of elements is less than or equal to zero.</exception>
|
||||||
public UnTypedArray(uint size, uint alignment, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
|
public UnTypedArray(nuint size, nuint alignment, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
|
||||||
: this(size, alignment, ref AllocationManager.GetAllocationHandle(allocator), allocationOption)
|
: this(size, alignment, ref AllocationManager.GetAllocationHandle(allocator), allocationOption)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -72,7 +73,7 @@ public unsafe struct UnTypedArray : IUnTypedCollection
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly ref T GetElementAt<T>(uint index)
|
public readonly ref T GetElementAt<T>(nuint index)
|
||||||
where T : unmanaged
|
where T : unmanaged
|
||||||
{
|
{
|
||||||
return ref UnsafeUtility.ReadArrayElementRef<T>(_buffer, index);
|
return ref UnsafeUtility.ReadArrayElementRef<T>(_buffer, index);
|
||||||
@@ -86,8 +87,10 @@ public unsafe struct UnTypedArray : IUnTypedCollection
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_buffer = _handle->Realloc(_handle->Allocator, _buffer, _size, newSize, _alignment, option);
|
MemoryHandle memHandle = _memoryHandle;
|
||||||
|
_buffer = _allocationHandle->Realloc(_allocationHandle->Allocator, _buffer, _size, newSize, _alignment, option, &memHandle);
|
||||||
_size = newSize;
|
_size = newSize;
|
||||||
|
_memoryHandle = memHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -112,12 +115,12 @@ public unsafe struct UnTypedArray : IUnTypedCollection
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_handle != null)
|
if (_allocationHandle != null)
|
||||||
{
|
{
|
||||||
_handle->Free(_handle->Allocator, _buffer);
|
_allocationHandle->Free(_allocationHandle->Allocator, _buffer, _memoryHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
_handle = null;
|
_allocationHandle = null;
|
||||||
_buffer = null;
|
_buffer = null;
|
||||||
_size = 0;
|
_size = 0;
|
||||||
_alignment = 0;
|
_alignment = 0;
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
|||||||
|
|
||||||
private T* _buffer;
|
private T* _buffer;
|
||||||
private int _count;
|
private int _count;
|
||||||
private AllocationHandle* _handle;
|
private MemoryHandle _memoryHandle;
|
||||||
|
private AllocationHandle* _allocationHandle;
|
||||||
|
|
||||||
public readonly int Count => _count;
|
public readonly int Count => _count;
|
||||||
|
|
||||||
@@ -73,14 +74,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly bool IsCreated
|
public readonly bool IsCreated => _buffer != null && _allocationHandle != null && _memoryHandle.IsValid;
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var handle = SafeHandle.GetSafeHandle(_buffer, AlignOf<T>());
|
|
||||||
return handle != null && Volatile.Read(ref handle->valid) == 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Enumerator GetEnumerator() => new((UnsafeArray<T>*)UnsafeUtility.AddressOf(ref this));
|
public Enumerator GetEnumerator() => new((UnsafeArray<T>*)UnsafeUtility.AddressOf(ref this));
|
||||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
|
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
|
||||||
@@ -108,15 +102,12 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
|||||||
throw new ArgumentOutOfRangeException(nameof(count), "Count can not be less than zero.");
|
throw new ArgumentOutOfRangeException(nameof(count), "Count can not be less than zero.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var tAlign = AlignOf<T>();
|
MemoryHandle memHandle;
|
||||||
var headerSize = SafeHandle.GetPaddedHeaderSize(tAlign);
|
var buff = handle.Alloc(handle.Allocator, (nuint)(count * sizeof(T)), AlignOf<T>(), allocationOption, &memHandle);
|
||||||
var sizeWithHeader = (nuint)(count * sizeof(T)) + headerSize;
|
|
||||||
var alignment = SafeHandle.GetAlignWithHeader(tAlign);
|
|
||||||
|
|
||||||
var buff = handle.Alloc(handle.Allocator, sizeWithHeader, alignment, allocationOption);
|
_buffer = (T*)buff;
|
||||||
|
_memoryHandle = memHandle;
|
||||||
_buffer = (T*)((byte*)buff + headerSize);
|
_allocationHandle = (AllocationHandle*)Unsafe.AsPointer(ref handle);
|
||||||
_handle = (AllocationHandle*)Unsafe.AsPointer(ref handle);
|
|
||||||
_count = count;
|
_count = count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,8 +179,10 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MemoryHandle memHandle = _memoryHandle;
|
||||||
var elemSize = SizeOf<T>();
|
var elemSize = SizeOf<T>();
|
||||||
_buffer = (T*)_handle->Realloc(_handle->Allocator, _buffer, (nuint)Count * elemSize, (nuint)newSize * elemSize, AlignOf<T>(), option);
|
_buffer = (T*)_allocationHandle->Realloc(_allocationHandle->Allocator, _buffer, (nuint)Count * elemSize, (nuint)newSize * elemSize, AlignOf<T>(), option, &memHandle);
|
||||||
|
_memoryHandle = memHandle;
|
||||||
_count = newSize;
|
_count = newSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,12 +231,12 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_handle != null)
|
if (_allocationHandle != null)
|
||||||
{
|
{
|
||||||
_handle->Free(_handle->Allocator, _buffer);
|
_allocationHandle->Free(_allocationHandle->Allocator, _buffer, _memoryHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
_handle = null;
|
_allocationHandle = null;
|
||||||
_buffer = null;
|
_buffer = null;
|
||||||
_count = 0;
|
_count = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Contracts;
|
using Misaki.HighPerformance.LowLevel.Contracts;
|
||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
|
using System.Collections;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Intrinsics.X86;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Misaki.HighPerformance.LowLevel.Collections;
|
namespace Misaki.HighPerformance.LowLevel.Collections;
|
||||||
@@ -28,15 +31,10 @@ public unsafe struct UnsafeBitSet : IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly int HighestBit => _highestBit;
|
public readonly int HighestBit => _highestBit;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the count of the bitset, how many uints it consists of.
|
|
||||||
/// </summary>
|
|
||||||
public readonly int Count => _bits.Count;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the total number of bits represented by the current instance.
|
/// Gets the total number of bits represented by the current instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly int BitCount => _bits.Count << _INDEX_SIZE;
|
public readonly int Count => _bits.Count << _INDEX_SIZE;
|
||||||
|
|
||||||
public readonly bool IsCreated => _bits.IsCreated;
|
public readonly bool IsCreated => _bits.IsCreated;
|
||||||
|
|
||||||
@@ -55,6 +53,7 @@ public unsafe struct UnsafeBitSet : IDisposable
|
|||||||
{
|
{
|
||||||
var uints = (minimalLength >> _INDEX_SIZE) + int.Sign(minimalLength & _BIT_SIZE);
|
var uints = (minimalLength >> _INDEX_SIZE) + int.Sign(minimalLength & _BIT_SIZE);
|
||||||
var length = RoundToPadding(uints);
|
var length = RoundToPadding(uints);
|
||||||
|
|
||||||
_bits = new UnsafeArray<uint>(length, ref handle, option);
|
_bits = new UnsafeArray<uint>(length, ref handle, option);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,9 +68,9 @@ public unsafe struct UnsafeBitSet : IDisposable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="UnsafeBitSet" /> class.
|
/// Initializes a new instance of the <see cref="UnsafeBitSet" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public UnsafeBitSet(Span<uint> bits, Allocator allocator, AllocationOption option = AllocationOption.None)
|
public UnsafeBitSet(Span<uint> bits, Allocator allocator)
|
||||||
{
|
{
|
||||||
_bits = new UnsafeArray<uint>(bits.Length, allocator, option);
|
_bits = new UnsafeArray<uint>(bits.Length, allocator, AllocationOption.None);
|
||||||
_bits.CopyFrom(bits);
|
_bits.CopyFrom(bits);
|
||||||
|
|
||||||
_highestBit = 0;
|
_highestBit = 0;
|
||||||
@@ -221,7 +220,7 @@ public unsafe struct UnsafeBitSet : IDisposable
|
|||||||
/// <returns>True if they match, false if not.</returns>
|
/// <returns>True if they match, false if not.</returns>
|
||||||
public readonly bool All(UnsafeBitSet other)
|
public readonly bool All(UnsafeBitSet other)
|
||||||
{
|
{
|
||||||
var min = Math.Min(Math.Min(Count, other.Count), _max);
|
var min = Math.Min(Math.Min(_bits.Count, other._bits.Count), _max);
|
||||||
if (!Vector.IsHardwareAccelerated || min < s_padding)
|
if (!Vector.IsHardwareAccelerated || min < s_padding)
|
||||||
{
|
{
|
||||||
var bits = _bits.AsSpan();
|
var bits = _bits.AsSpan();
|
||||||
@@ -282,7 +281,7 @@ public unsafe struct UnsafeBitSet : IDisposable
|
|||||||
/// <returns>True if they match, false if not.</returns>
|
/// <returns>True if they match, false if not.</returns>
|
||||||
public readonly bool Any(UnsafeBitSet other)
|
public readonly bool Any(UnsafeBitSet other)
|
||||||
{
|
{
|
||||||
var min = Math.Min(Math.Min(Count, other.Count), _max);
|
var min = Math.Min(Math.Min(_bits.Count, other._bits.Count), _max);
|
||||||
if (!Vector.IsHardwareAccelerated || min < s_padding)
|
if (!Vector.IsHardwareAccelerated || min < s_padding)
|
||||||
{
|
{
|
||||||
var bits = _bits.AsSpan();
|
var bits = _bits.AsSpan();
|
||||||
@@ -343,7 +342,7 @@ public unsafe struct UnsafeBitSet : IDisposable
|
|||||||
/// <returns>True if none match, false if not.</returns>
|
/// <returns>True if none match, false if not.</returns>
|
||||||
public readonly bool None(UnsafeBitSet other)
|
public readonly bool None(UnsafeBitSet other)
|
||||||
{
|
{
|
||||||
var min = Math.Min(Math.Min(Count, other.Count), _max);
|
var min = Math.Min(Math.Min(_bits.Count, other._bits.Count), _max);
|
||||||
if (!Vector.IsHardwareAccelerated || min < s_padding)
|
if (!Vector.IsHardwareAccelerated || min < s_padding)
|
||||||
{
|
{
|
||||||
var bits = _bits.AsSpan();
|
var bits = _bits.AsSpan();
|
||||||
@@ -385,7 +384,7 @@ public unsafe struct UnsafeBitSet : IDisposable
|
|||||||
/// <returns>True if they match, false if not.</returns>
|
/// <returns>True if they match, false if not.</returns>
|
||||||
public readonly bool Exclusive(UnsafeBitSet other)
|
public readonly bool Exclusive(UnsafeBitSet other)
|
||||||
{
|
{
|
||||||
var min = Math.Min(Math.Min(Count, other.Count), _max);
|
var min = Math.Min(Math.Min(_bits.Count, other._bits.Count), _max);
|
||||||
|
|
||||||
if (!Vector.IsHardwareAccelerated || min < s_padding)
|
if (!Vector.IsHardwareAccelerated || min < s_padding)
|
||||||
{
|
{
|
||||||
@@ -440,83 +439,213 @@ public unsafe struct UnsafeBitSet : IDisposable
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inverts all bits in the current vector, replacing each bit with its logical complement.
|
||||||
|
/// </summary>
|
||||||
|
public void Not()
|
||||||
|
{
|
||||||
|
var thisCount = _bits.Count;
|
||||||
|
if (!Vector.IsHardwareAccelerated || thisCount < s_padding)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < thisCount; i++)
|
||||||
|
{
|
||||||
|
_bits[i] = ~_bits[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var pThis = (byte*)_bits.GetUnsafePtr();
|
||||||
|
|
||||||
|
for (var i = 0; i < thisCount; i += s_padding)
|
||||||
|
{
|
||||||
|
var vector = new Vector<uint>(_bits.AsSpan()[i..]);
|
||||||
|
var resultVector = ~vector;
|
||||||
|
|
||||||
|
Unsafe.WriteUnaligned(pThis + i, resultVector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs a bitwise AND operation between the current bit set and the specified bit set, updating the current bit
|
||||||
|
/// set in place.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other">The bit set to combine with the current bit set using a bitwise AND operation. Must have the same length as the current bit set.</param>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when <paramref name="other"/> does not have the same length as the current bit set.</exception>
|
||||||
public void And(UnsafeBitSet other)
|
public void And(UnsafeBitSet other)
|
||||||
{
|
{
|
||||||
if (Count != other.Count)
|
var thisCount = _bits.Count;
|
||||||
|
if (thisCount != other._bits.Count)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Bitsets must be of the same length for AND operation.");
|
throw new ArgumentException("Bitsets must be of the same length for AND operation.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Vector.IsHardwareAccelerated || Count < s_padding)
|
if (!Vector.IsHardwareAccelerated || thisCount < s_padding)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < Count; i++)
|
for (var i = 0; i < thisCount; i++)
|
||||||
{
|
{
|
||||||
_bits[i] &= other._bits[i];
|
_bits[i] &= other._bits[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (var i = 0; i < Count; i += s_padding)
|
var pThis = (byte*)_bits.GetUnsafePtr();
|
||||||
|
var pOther = (byte*)other._bits.GetUnsafePtr();
|
||||||
|
|
||||||
|
for (var i = 0; i < thisCount; i += s_padding)
|
||||||
{
|
{
|
||||||
var vectorLeft = new Vector<uint>(_bits.AsSpan()[i..]);
|
var vectorLeft = Vector.Load(pThis + i);
|
||||||
var vectorRight = new Vector<uint>(other._bits.AsSpan()[i..]);
|
var vectorRight = Vector.Load(pOther + i);
|
||||||
var resultVector = Vector.BitwiseAnd(vectorLeft, vectorRight);
|
var resultVector = Vector.BitwiseAnd(vectorLeft, vectorRight);
|
||||||
|
|
||||||
resultVector.CopyTo(_bits.AsSpan(i, s_padding));
|
Unsafe.WriteUnaligned(pThis + i, resultVector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Or(UnsafeBitSet other)
|
/// <summary>
|
||||||
|
/// Performs a bitwise NAND operation between the current bit set and the specified bit set, updating the current
|
||||||
|
/// bit set in place.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other">The bit set to combine with the current bit set using the NAND operation. Must have the same length as the current bit set.</param>
|
||||||
|
/// <exception cref="ArgumentException">Thrown if <paramref name="other"/> does not have the same length as the current bit set.</exception>
|
||||||
|
public void Nand(UnsafeBitSet other)
|
||||||
{
|
{
|
||||||
if (Count != other.Count)
|
var thisCount = _bits.Count;
|
||||||
|
if (thisCount != other._bits.Count)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Bitsets must be of the same length for AND operation.");
|
throw new ArgumentException("Bitsets must be of the same length for AND operation.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Vector.IsHardwareAccelerated || Count < s_padding)
|
if (!Vector.IsHardwareAccelerated || thisCount < s_padding)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < Count; i++)
|
for (var i = 0; i < thisCount; i++)
|
||||||
|
{
|
||||||
|
_bits[i] = ~(_bits[i] & other._bits[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var pThis = (byte*)_bits.GetUnsafePtr();
|
||||||
|
var pOther = (byte*)other._bits.GetUnsafePtr();
|
||||||
|
|
||||||
|
for (var i = 0; i < thisCount; i += s_padding)
|
||||||
|
{
|
||||||
|
var vectorLeft = Vector.Load(pThis +i);
|
||||||
|
var vectorRight = Vector.Load(pOther +i);
|
||||||
|
var resultVector = ~Vector.BitwiseAnd(vectorLeft, vectorRight);
|
||||||
|
|
||||||
|
Unsafe.WriteUnaligned(pThis + i, resultVector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs a bitwise AND NOT operation between the current bit set and the specified bit set, updating the current
|
||||||
|
/// bit set in place.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other">The bit set whose bits will be inverted and ANDed with the current bit set. Must have the same length as the current bit set.</param>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the specified bit set does not have the same length as the current bit set.</exception>
|
||||||
|
public void ANDC(UnsafeBitSet other)
|
||||||
|
{
|
||||||
|
var thisCount = _bits.Count;
|
||||||
|
if (thisCount != other._bits.Count)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Bitsets must be of the same length for AND operation.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Vector.IsHardwareAccelerated || thisCount < s_padding)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < thisCount; i++)
|
||||||
|
{
|
||||||
|
_bits[i] &= ~other._bits[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var pThis = (byte*)_bits.GetUnsafePtr();
|
||||||
|
var pOther = (byte*)other._bits.GetUnsafePtr();
|
||||||
|
|
||||||
|
for (var i = 0; i < thisCount; i += s_padding)
|
||||||
|
{
|
||||||
|
var vectorLeft = Vector.Load(pThis + i);
|
||||||
|
var vectorRight = Vector.Load(pOther + i);
|
||||||
|
var resultVector = Vector.AndNot(vectorLeft, vectorRight);
|
||||||
|
|
||||||
|
Unsafe.WriteUnaligned(pThis + i, resultVector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs a bitwise OR operation between the current bit set and the specified bit set, updating the current set
|
||||||
|
/// in place.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other">The bit set to combine with the current set using a bitwise OR operation. Must have the same length as the current bit set.</param>
|
||||||
|
/// <exception cref="ArgumentException">Thrown if <paramref name="other"/> does not have the same length as the current bit set.</exception>
|
||||||
|
public void Or(UnsafeBitSet other)
|
||||||
|
{
|
||||||
|
var thisCount = _bits.Count;
|
||||||
|
if (thisCount != other._bits.Count)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Bitsets must be of the same length for AND operation.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Vector.IsHardwareAccelerated || thisCount < s_padding)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < thisCount; i++)
|
||||||
{
|
{
|
||||||
_bits[i] |= other._bits[i];
|
_bits[i] |= other._bits[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (var i = 0; i < Count; i += s_padding)
|
var pThis = (byte*)_bits.GetUnsafePtr();
|
||||||
|
var pOther = (byte*)other._bits.GetUnsafePtr();
|
||||||
|
|
||||||
|
for (var i = 0; i < thisCount; i += s_padding)
|
||||||
{
|
{
|
||||||
var vectorLeft = new Vector<uint>(_bits.AsSpan()[i..]);
|
var vectorLeft = Vector.Load(pThis + i);
|
||||||
var vectorRight = new Vector<uint>(other._bits.AsSpan()[i..]);
|
var vectorRight = Vector.Load(pOther + i);
|
||||||
var resultVector = Vector.BitwiseOr(vectorLeft, vectorRight);
|
var resultVector = Vector.BitwiseOr(vectorLeft, vectorRight);
|
||||||
|
|
||||||
resultVector.CopyTo(_bits.AsSpan(i, s_padding));
|
Unsafe.WriteUnaligned(pThis + i, resultVector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Performs a bitwise exclusive OR (XOR) operation between the current bit set and the specified bit set.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other">The bit set to XOR with the current instance. Must have the same length as the current bit set.</param>
|
||||||
|
/// <exception cref="ArgumentException">Thrown if <paramref name="other"/> does not have the same length as the current bit set.</exception>
|
||||||
public void Xor(UnsafeBitSet other)
|
public void Xor(UnsafeBitSet other)
|
||||||
{
|
{
|
||||||
if (Count != other.Count)
|
var thisCount = _bits.Count;
|
||||||
|
if (thisCount != other._bits.Count)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Bitsets must be of the same length for AND operation.");
|
throw new ArgumentException("Bitsets must be of the same length for AND operation.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Vector.IsHardwareAccelerated || Count < s_padding)
|
if (!Vector.IsHardwareAccelerated || thisCount < s_padding)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < Count; i++)
|
for (var i = 0; i < thisCount; i++)
|
||||||
{
|
{
|
||||||
_bits[i] ^= other._bits[i];
|
_bits[i] ^= other._bits[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (var i = 0; i < Count; i += s_padding)
|
var pThis = (byte*)_bits.GetUnsafePtr();
|
||||||
|
var pOther = (byte*)other._bits.GetUnsafePtr();
|
||||||
|
|
||||||
|
for (var i = 0; i < thisCount; i += s_padding)
|
||||||
{
|
{
|
||||||
var vectorLeft = new Vector<uint>(_bits.AsSpan()[i..]);
|
var vectorLeft = Vector.Load(pThis + i);
|
||||||
var vectorRight = new Vector<uint>(other._bits.AsSpan()[i..]);
|
var vectorRight = Vector.Load(pOther + i);
|
||||||
var resultVector = Vector.Xor(vectorLeft, vectorRight);
|
var resultVector = Vector.Xor(vectorLeft, vectorRight);
|
||||||
|
|
||||||
resultVector.CopyTo(_bits.AsSpan(i, s_padding));
|
Unsafe.WriteUnaligned(pThis + i, resultVector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -524,7 +653,7 @@ public unsafe struct UnsafeBitSet : IDisposable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a <see cref="Span{T}"/> to access the <see cref="_bits"/>.
|
/// Creates a <see cref="Span{T}"/> to access the <see cref="_bits"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The hash.</returns>
|
/// <returns>The <see cref="Span{T}"/>.</returns>
|
||||||
public readonly Span<uint> AsSpan()
|
public readonly Span<uint> AsSpan()
|
||||||
{
|
{
|
||||||
var max = _highestBit / (_BIT_SIZE + 1) + 1;
|
var max = _highestBit / (_BIT_SIZE + 1) + 1;
|
||||||
@@ -540,7 +669,7 @@ public unsafe struct UnsafeBitSet : IDisposable
|
|||||||
public readonly Span<uint> AsSpan(Span<uint> span, bool zero = true)
|
public readonly Span<uint> AsSpan(Span<uint> span, bool zero = true)
|
||||||
{
|
{
|
||||||
// Copy everything thats possible from one to another
|
// Copy everything thats possible from one to another
|
||||||
var length = Math.Min(Count, span.Length);
|
var length = Math.Min(_bits.Count, span.Length);
|
||||||
for (var index = 0; index < length; index++)
|
for (var index = 0; index < length; index++)
|
||||||
{
|
{
|
||||||
span[index] = _bits[index];
|
span[index] = _bits[index];
|
||||||
@@ -552,7 +681,7 @@ public unsafe struct UnsafeBitSet : IDisposable
|
|||||||
span[index] = 0;
|
span[index] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return span[..Count];
|
return span[.._bits.Count];
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly override string ToString()
|
public readonly override string ToString()
|
||||||
|
|||||||
@@ -58,10 +58,10 @@ public readonly unsafe struct AllocationHandle
|
|||||||
/// Represents an allocator interface for managing memory allocations.
|
/// Represents an allocator interface for managing memory allocations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// The allocator must be static or pined to a specific memory region.
|
/// The allocator must be pined to a specific memory region.
|
||||||
/// Otherwise the pointer of the allocator, <see cref="AllocationHandle.Allocator"/>, may become invalid and lead to undefined behavior.
|
/// Otherwise the reference of the <see cref="AllocationHandle.Allocator"/>, may become invalid and lead to undefined behavior.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public unsafe interface IAllocator
|
public interface IAllocator
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a reference to the allocation handle associated with this allocator.
|
/// Gets a reference to the allocation handle associated with this allocator.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
<Authors>Misaki</Authors>
|
<Authors>Misaki</Authors>
|
||||||
<AssemblyVersion>1.2.0</AssemblyVersion>
|
<AssemblyVersion>1.2.1</AssemblyVersion>
|
||||||
<Version>$(AssemblyVersion)</Version>
|
<Version>$(AssemblyVersion)</Version>
|
||||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>
|
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>
|
||||||
@@ -15,12 +15,17 @@
|
|||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
|
<DefineConstants>$(DefineConstants);ENABLE_COLLECTION_CHECKS</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Misaki.HighPerformance\Misaki.HighPerformance.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Update="Collections\FixedText.tt">
|
<None Update="Collections\FixedText.tt">
|
||||||
<Generator>TextTemplatingFileGenerator</Generator>
|
<Generator>TextTemplatingFileGenerator</Generator>
|
||||||
|
|||||||
@@ -77,7 +77,8 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
|||||||
private readonly int _sizeOfTValue;
|
private readonly int _sizeOfTValue;
|
||||||
private readonly int _log2MinGrowth;
|
private readonly int _log2MinGrowth;
|
||||||
|
|
||||||
private AllocationHandle* _handle;
|
private MemoryHandle _memoryHandle;
|
||||||
|
private AllocationHandle* _allocationHandle;
|
||||||
|
|
||||||
public const int MINIMAL_CAPACITY = 64;
|
public const int MINIMAL_CAPACITY = 64;
|
||||||
|
|
||||||
@@ -89,43 +90,22 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
|||||||
|
|
||||||
public readonly bool IsEmpty => !IsCreated || _count == 0;
|
public readonly bool IsEmpty => !IsCreated || _count == 0;
|
||||||
|
|
||||||
public readonly bool IsCreated
|
public readonly bool IsCreated => _buffer != null && _allocationHandle != null && _memoryHandle.IsValid;
|
||||||
|
|
||||||
|
private static int CalculateDataSize(int capacity, int bucketCapacity, int sizeOfTValue, out int outKeyOffset, out int outNextOffset, out int outBucketOffset)
|
||||||
{
|
{
|
||||||
get
|
var sizeOfTKey = sizeof(TKey);
|
||||||
{
|
var sizeOfInt = sizeof(int);
|
||||||
var handle = SafeHandle.GetSafeHandle(_buffer, (nuint)_alignment);
|
|
||||||
return handle != null && Volatile.Read(ref handle->valid) == 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int CalculateDataSize(int capacity, int bucketCapacity, int sizeOfTValue, int alignment, out int outValueOffset, out int outKeyOffset, out int outNextOffset, out int outBucketOffset)
|
var valuesSize = sizeOfTValue * capacity;
|
||||||
{
|
var keysSize = sizeOfTKey * capacity;
|
||||||
static int AlignUp(int size, int align)
|
var nextSize = sizeOfInt * capacity;
|
||||||
{
|
var bucketSize = sizeOfInt * bucketCapacity;
|
||||||
return (size + (align - 1)) & ~(align - 1);
|
var totalSize = valuesSize + keysSize + nextSize + bucketSize;
|
||||||
}
|
|
||||||
|
|
||||||
var headerSize = SafeHandle.GetPaddedHeaderSize((nuint)alignment);
|
outKeyOffset = 0 + valuesSize;
|
||||||
|
outNextOffset = outKeyOffset + keysSize;
|
||||||
var valuesSize = (sizeOfTValue * capacity);
|
outBucketOffset = outNextOffset + nextSize;
|
||||||
var valuesSizePadded = AlignUp(valuesSize, alignment);
|
|
||||||
|
|
||||||
var keysSize = (sizeof(TKey) * capacity);
|
|
||||||
var keysSizePadded = AlignUp(keysSize, alignment);
|
|
||||||
|
|
||||||
var nextSize = (sizeof(int) * capacity);
|
|
||||||
var nextSizePadded = AlignUp(nextSize, alignment);
|
|
||||||
|
|
||||||
// Buckets are the last item, doesn't need padding after it
|
|
||||||
var bucketSize = (sizeof(int) * bucketCapacity);
|
|
||||||
|
|
||||||
outValueOffset = (int)headerSize;
|
|
||||||
outKeyOffset = outValueOffset + valuesSizePadded;
|
|
||||||
outNextOffset = outKeyOffset + keysSizePadded;
|
|
||||||
outBucketOffset = outNextOffset + nextSizePadded;
|
|
||||||
|
|
||||||
// Total size is header + all buffers
|
|
||||||
var totalSize = (int)headerSize + outKeyOffset + keysSizePadded + nextSizePadded + bucketSize;
|
|
||||||
|
|
||||||
return totalSize;
|
return totalSize;
|
||||||
}
|
}
|
||||||
@@ -149,16 +129,16 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
|||||||
var alignOfInt = (int)AlignOf<int>();
|
var alignOfInt = (int)AlignOf<int>();
|
||||||
var maxDataAlign = Math.Max(Math.Max(alignOfTValue, alignOfKey), alignOfInt);
|
var maxDataAlign = Math.Max(Math.Max(alignOfTValue, alignOfKey), alignOfInt);
|
||||||
|
|
||||||
_alignment = (int)SafeHandle.GetAlignWithHeader((nuint)maxDataAlign);
|
_alignment = maxDataAlign;
|
||||||
_sizeOfTValue = sizeOfTValue;
|
_sizeOfTValue = sizeOfTValue;
|
||||||
_log2MinGrowth = BitOperations.Log2(minGrowth);
|
_log2MinGrowth = BitOperations.Log2(minGrowth);
|
||||||
|
|
||||||
_handle = (AllocationHandle*)Unsafe.AsPointer(ref handle);
|
_allocationHandle = (AllocationHandle*)Unsafe.AsPointer(ref handle);
|
||||||
|
|
||||||
var totalSize = CalculateDataSize(_capacity, _bucketCapacity, sizeOfTValue, _alignment,
|
var totalSize = CalculateDataSize(_capacity, _bucketCapacity, sizeOfTValue,
|
||||||
out var valueOffset, out var keyOffset, out var nextOffset, out var bucketOffset);
|
out var keyOffset, out var nextOffset, out var bucketOffset);
|
||||||
|
|
||||||
AllocateBuffer(totalSize, valueOffset, keyOffset, nextOffset, bucketOffset, _alignment, allocationOption);
|
AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset, allocationOption);
|
||||||
Clear();
|
Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,28 +190,31 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void AllocateBuffer(int totalSize, int valueOffset, int keyOffset, int nextOffset, int bucketOffset, int alignment, AllocationOption allocationOption)
|
private void AllocateBuffer(int totalSize, int keyOffset, int nextOffset, int bucketOffset, AllocationOption allocationOption)
|
||||||
{
|
{
|
||||||
var buf = (byte*)_handle->Alloc(_handle->Allocator, (uint)totalSize, (nuint)alignment, allocationOption);
|
MemoryHandle memHandle;
|
||||||
|
var buf = (byte*)_allocationHandle->Alloc(_allocationHandle->Allocator, (uint)totalSize, (nuint)_alignment, allocationOption, &memHandle);
|
||||||
|
|
||||||
_buffer = buf + valueOffset;
|
_buffer = buf;
|
||||||
_keys = (TKey*)(_buffer + keyOffset);
|
_keys = (TKey*)(_buffer + keyOffset);
|
||||||
_next = (int*)(_buffer + nextOffset);
|
_next = (int*)(_buffer + nextOffset);
|
||||||
_buckets = (int*)(_buffer + bucketOffset);
|
_buckets = (int*)(_buffer + bucketOffset);
|
||||||
|
_memoryHandle = memHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResizeExact(int newCapacity, int newBucketCapacity)
|
private void ResizeExact(int newCapacity, int newBucketCapacity)
|
||||||
{
|
{
|
||||||
var totalSize = CalculateDataSize(newCapacity, newBucketCapacity, _sizeOfTValue, _alignment,
|
var totalSize = CalculateDataSize(newCapacity, newBucketCapacity, _sizeOfTValue,
|
||||||
out var valueOffset, out var keyOffset, out var nextOffset, out var bucketOffset);
|
out var keyOffset, out var nextOffset, out var bucketOffset);
|
||||||
|
|
||||||
var oldBuffer = _buffer;
|
var oldBuffer = _buffer;
|
||||||
var oldKeys = _keys;
|
var oldKeys = _keys;
|
||||||
var oldNext = _next;
|
var oldNext = _next;
|
||||||
var oldBuckets = _buckets;
|
var oldBuckets = _buckets;
|
||||||
var oldBucketCapacity = _bucketCapacity;
|
var oldBucketCapacity = _bucketCapacity;
|
||||||
|
var oldMemoryHandle = _memoryHandle;
|
||||||
|
|
||||||
AllocateBuffer(totalSize, valueOffset, keyOffset, nextOffset, bucketOffset, _alignment, AllocationOption.None);
|
AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset, AllocationOption.None);
|
||||||
_capacity = newCapacity;
|
_capacity = newCapacity;
|
||||||
_bucketCapacity = newBucketCapacity;
|
_bucketCapacity = newBucketCapacity;
|
||||||
|
|
||||||
@@ -246,7 +229,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_handle->Free(_handle->Allocator, oldBuffer);
|
_allocationHandle->Free(_allocationHandle->Allocator, oldBuffer, oldMemoryHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Resize(int newCapacity)
|
public void Resize(int newCapacity)
|
||||||
@@ -540,9 +523,9 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_handle != null)
|
if (_allocationHandle != null)
|
||||||
{
|
{
|
||||||
_handle->Free(_handle->Allocator, _buffer);
|
_allocationHandle->Free(_allocationHandle->Allocator, _buffer, _memoryHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
_buffer = null;
|
_buffer = null;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.Intrinsics;
|
using System.Runtime.Intrinsics;
|
||||||
@@ -119,7 +119,7 @@ public static unsafe partial class MemoryUtility
|
|||||||
{
|
{
|
||||||
if ((((uint)searchSpace + offset) & (nuint)(Vector256<byte>.Count - 1)) != 0)
|
if ((((uint)searchSpace + offset) & (nuint)(Vector256<byte>.Count - 1)) != 0)
|
||||||
{
|
{
|
||||||
// Not currently aligned to Vector256 (is aligned to Vector128); this can cause a problem for searches
|
// Invert currently aligned to Vector256 (is aligned to Vector128); this can cause a problem for searches
|
||||||
// with no upper bound e.g. String.strlen.
|
// with no upper bound e.g. String.strlen.
|
||||||
// Start with a check on Vector128 to align to Vector256, before moving to processing Vector256.
|
// Start with a check on Vector128 to align to Vector256, before moving to processing Vector256.
|
||||||
// This ensures we do not fault across memory pages while searching for an end of string.
|
// This ensures we do not fault across memory pages while searching for an end of string.
|
||||||
@@ -141,7 +141,7 @@ public static unsafe partial class MemoryUtility
|
|||||||
|
|
||||||
if ((((uint)searchSpace + offset) & (nuint)(Vector512<byte>.Count - 1)) != 0)
|
if ((((uint)searchSpace + offset) & (nuint)(Vector512<byte>.Count - 1)) != 0)
|
||||||
{
|
{
|
||||||
// Not currently aligned to Vector512 (is aligned to Vector256); this can cause a problem for searches
|
// Invert currently aligned to Vector512 (is aligned to Vector256); this can cause a problem for searches
|
||||||
// with no upper bound e.g. String.strlen.
|
// with no upper bound e.g. String.strlen.
|
||||||
// Start with a check on Vector256 to align to Vector512, before moving to processing Vector256.
|
// Start with a check on Vector256 to align to Vector512, before moving to processing Vector256.
|
||||||
// This ensures we do not fault across memory pages while searching for an end of string.
|
// This ensures we do not fault across memory pages while searching for an end of string.
|
||||||
@@ -232,7 +232,7 @@ public static unsafe partial class MemoryUtility
|
|||||||
{
|
{
|
||||||
if ((((uint)searchSpace + offset) & (nuint)(Vector256<byte>.Count - 1)) != 0)
|
if ((((uint)searchSpace + offset) & (nuint)(Vector256<byte>.Count - 1)) != 0)
|
||||||
{
|
{
|
||||||
// Not currently aligned to Vector256 (is aligned to Vector128); this can cause a problem for searches
|
// Invert currently aligned to Vector256 (is aligned to Vector128); this can cause a problem for searches
|
||||||
// with no upper bound e.g. String.strlen.
|
// with no upper bound e.g. String.strlen.
|
||||||
// Start with a check on Vector128 to align to Vector256, before moving to processing Vector256.
|
// Start with a check on Vector128 to align to Vector256, before moving to processing Vector256.
|
||||||
// This ensures we do not fault across memory pages while searching for an end of string.
|
// This ensures we do not fault across memory pages while searching for an end of string.
|
||||||
|
|||||||
@@ -92,11 +92,6 @@ public static unsafe partial class MemoryUtility
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void MemClear(void* ptr, nuint size)
|
public static void MemClear(void* ptr, nuint size)
|
||||||
{
|
{
|
||||||
if (ptr == null || size == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeMemory.Clear(ptr, size);
|
NativeMemory.Clear(ptr, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,11 +104,6 @@ public static unsafe partial class MemoryUtility
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void MemSet(void* ptr, byte value, nuint size)
|
public static void MemSet(void* ptr, byte value, nuint size)
|
||||||
{
|
{
|
||||||
if (ptr == null || size == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeMemory.Fill(ptr, size, value);
|
NativeMemory.Fill(ptr, size, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,11 +116,6 @@ public static unsafe partial class MemoryUtility
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void MemCpy(void* source, void* destination, nuint size)
|
public static void MemCpy(void* source, void* destination, nuint size)
|
||||||
{
|
{
|
||||||
if (source == null || destination == null || size == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeMemory.Copy(source, destination, size);
|
NativeMemory.Copy(source, destination, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,9 @@
|
|||||||
|
|
||||||
//Console.WriteLine($"Count should be {threadCount * 990}, actual: {map.Count}");
|
//Console.WriteLine($"Count should be {threadCount * 990}, actual: {map.Count}");
|
||||||
|
|
||||||
using Misaki.HighPerformance.LowLevel;
|
//using Misaki.HighPerformance.LowLevel;
|
||||||
|
|
||||||
BenchmarkDotNet.Running.BenchmarkRunner.Run<Misaki.HighPerformance.Test.Benchmark.CollectionBenchmark>();
|
//BenchmarkDotNet.Running.BenchmarkRunner.Run<Misaki.HighPerformance.Test.Benchmark.CollectionBenchmark>();
|
||||||
|
|
||||||
//using Misaki.HighPerformance.LowLevel.Buffer;
|
//using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
//using Misaki.HighPerformance.LowLevel.Collections;
|
//using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
@@ -40,28 +40,16 @@ BenchmarkDotNet.Running.BenchmarkRunner.Run<Misaki.HighPerformance.Test.Benchmar
|
|||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
//var arr1 = new Misaki.HighPerformance.LowLevel.Collections.UnsafeArray<int>(10, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
|
var arr1 = new Misaki.HighPerformance.LowLevel.Collections.UnsafeArray<int>(10, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
|
||||||
//var arr2 = arr1;
|
var arr2 = arr1;
|
||||||
|
|
||||||
//arr1.Dispose();
|
arr1.Dispose();
|
||||||
//try
|
try
|
||||||
//{
|
|
||||||
// arr2[0] = 42; // This should throw an exception because arr1 has been disposed.
|
|
||||||
//}
|
|
||||||
//catch (Exception ex)
|
|
||||||
//{
|
|
||||||
// Console.WriteLine($"Caught expected exception: {ex.Message}");
|
|
||||||
//}
|
|
||||||
//arr2.Dispose(); // This should not cause a double free error because of safe handle.
|
|
||||||
|
|
||||||
var a = new UniquePtr<MyStruct>();
|
|
||||||
unsafe
|
|
||||||
{
|
{
|
||||||
var b = a.Share();
|
arr2[0] = 42; // This should throw an exception because arr1 has been disposed.
|
||||||
b.Get()->Value = 42;
|
arr2.Dispose();
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
internal struct MyStruct
|
|
||||||
{
|
{
|
||||||
public int Value;
|
Console.WriteLine($"Caught expected exception: {ex.Message}");
|
||||||
}
|
}
|
||||||
@@ -28,7 +28,7 @@ public class TestAllocationManager
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
var leaks = AllocationManager.LiveHeapAllocationCount;
|
var leaks = AllocationManager.LiveAllocationCount;
|
||||||
Assert.AreEqual(0, leaks);
|
Assert.AreEqual(0, leaks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ public class TestAllocationManager
|
|||||||
}
|
}
|
||||||
catch (MemoryLeakException)
|
catch (MemoryLeakException)
|
||||||
{
|
{
|
||||||
var leaks = AllocationManager.LiveHeapAllocationCount;
|
var leaks = AllocationManager.LiveAllocationCount;
|
||||||
Assert.AreEqual(2, leaks);
|
Assert.AreEqual(2, leaks);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Xml.Serialization;
|
|
||||||
|
|
||||||
namespace Misaki.HighPerformance.Test.UnitTest.Collections;
|
namespace Misaki.HighPerformance.Test.UnitTest.Collections;
|
||||||
|
|
||||||
@@ -32,7 +26,7 @@ public class TestUnsafeBitSet
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TestBitCount()
|
public void TestBitCount()
|
||||||
{
|
{
|
||||||
Assert.AreEqual(256, _set1.BitCount);
|
Assert.AreEqual(256, _set1.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@@ -48,20 +42,20 @@ public class TestUnsafeBitSet
|
|||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TestClearAll()
|
public void TestClearAll()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _set1.BitCount; i++)
|
for (var i = 0; i < _set1.Count; i++)
|
||||||
{
|
{
|
||||||
_set1.SetBit(i);
|
_set1.SetBit(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
_set1.ClearAll();
|
_set1.ClearAll();
|
||||||
for (int i = 0; i < _set1.BitCount; i++)
|
for (var i = 0; i < _set1.Count; i++)
|
||||||
{
|
{
|
||||||
Assert.IsFalse(_set1.IsSet(i));
|
Assert.IsFalse(_set1.IsSet(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TestAndOperation()
|
public void TestAnd()
|
||||||
{
|
{
|
||||||
_set1.SetBit(0);
|
_set1.SetBit(0);
|
||||||
_set1.SetBit(1);
|
_set1.SetBit(1);
|
||||||
@@ -75,4 +69,41 @@ public class TestUnsafeBitSet
|
|||||||
Assert.IsTrue(_set1.IsSet(1));
|
Assert.IsTrue(_set1.IsSet(1));
|
||||||
Assert.IsFalse(_set1.IsSet(2));
|
Assert.IsFalse(_set1.IsSet(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestNot()
|
||||||
|
{
|
||||||
|
_set1.SetBit(0);
|
||||||
|
_set1.SetBit(1);
|
||||||
|
|
||||||
|
_set2.SetBit(1);
|
||||||
|
_set2.SetBit(2);
|
||||||
|
|
||||||
|
_set1.Not();
|
||||||
|
_set2.Not();
|
||||||
|
|
||||||
|
Assert.IsFalse(_set1.IsSet(0));
|
||||||
|
Assert.IsFalse(_set1.IsSet(1));
|
||||||
|
Assert.IsTrue(_set1.IsSet(2));
|
||||||
|
Assert.IsTrue(_set1.IsSet(3));
|
||||||
|
Assert.IsTrue(_set2.IsSet(0));
|
||||||
|
Assert.IsFalse(_set2.IsSet(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestANDC()
|
||||||
|
{
|
||||||
|
_set1.SetBit(0);
|
||||||
|
_set1.SetBit(1);
|
||||||
|
|
||||||
|
_set2.SetBit(1);
|
||||||
|
_set2.SetBit(2);
|
||||||
|
|
||||||
|
_set1.ANDC(_set2);
|
||||||
|
|
||||||
|
Assert.IsTrue(_set1.IsSet(0));
|
||||||
|
Assert.IsFalse(_set1.IsSet(1));
|
||||||
|
Assert.IsFalse(_set1.IsSet(2));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user