feat(allocator): add unified Reallocate to all allocators
Introduce a unified Reallocate method to all memory allocator types (Arena, Stack, FreeList, VirtualArena, VirtualStack, DynamicArena) and require it in the IMemoryAllocator interface. This enables efficient resizing of memory blocks, with fast-path optimizations for stack-like allocators. Update AllocationManager and MemoryPool to use the new Reallocate method, simplifying and optimizing memory resizing logic. Add public properties for buffer pointers, sizes, and offsets to allocator structs for easier diagnostics. Set FreeList's default concurrency level to 1 and make its allocation method return null on dispose instead of throwing. Clean up vector types for formatting, fix UnsafeList's RemoveRangeSwapBack logic, and simplify RemoveAtSwapBack. Simplify Program.cs to only run SPMDBenchmark. Add new unit tests for FixedString, UnsafeList, UnsafeHashMap, and UnsafeHashSet. Apply minor test code cleanups for consistency in TestUnsafeQueue. BREAKING CHANGE: IMemoryAllocator now requires a Reallocate method, and allocator APIs have changed accordingly.
This commit is contained in:
@@ -129,23 +129,8 @@ public static unsafe class AllocationManager
|
|||||||
#endif
|
#endif
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (ptr == null)
|
|
||||||
{
|
|
||||||
return Allocate(instance, newSize, alignment, allocationOption
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, pHandle
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var selfPtr = (ArenaAllocator*)instance;
|
var selfPtr = (ArenaAllocator*)instance;
|
||||||
var newPtr = selfPtr->_arena.Allocate(newSize, alignment, allocationOption);
|
var newPtr = selfPtr->_arena.Reallocate(ptr, oldSize, newSize, alignment, allocationOption);
|
||||||
if (newPtr == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
|
||||||
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
*pHandle = new MemoryHandle(_ARENA_MAGIC_ID, selfPtr->_currentTick);
|
*pHandle = new MemoryHandle(_ARENA_MAGIC_ID, selfPtr->_currentTick);
|
||||||
@@ -370,44 +355,8 @@ public static unsafe class AllocationManager
|
|||||||
#endif
|
#endif
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (ptr == null)
|
|
||||||
{
|
|
||||||
return Allocate(instance, newSize, alignment, allocationOption
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, pHandle
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
EnsureInitialize();
|
EnsureInitialize();
|
||||||
|
var newPtr = s_stack.Reallocate(ptr, oldSize, newSize, alignment, allocationOption);
|
||||||
// Optimize for last allocation. Set offset directly.
|
|
||||||
var oldBase = s_stack.Buffer + s_stack.Offset - oldSize;
|
|
||||||
if (ptr == oldBase)
|
|
||||||
{
|
|
||||||
if (newSize > oldSize)
|
|
||||||
{
|
|
||||||
var diff = newSize - oldSize;
|
|
||||||
s_stack.Offset += diff;
|
|
||||||
if (allocationOption.HasFlag(AllocationOption.Clear))
|
|
||||||
{
|
|
||||||
MemClear(s_stack.Buffer + s_stack.Offset - diff, diff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
*pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset);
|
|
||||||
#endif
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
var newPtr = s_stack.Allocate(newSize, alignment, allocationOption);
|
|
||||||
if (newPtr == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
|
||||||
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
*pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset);
|
*pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset);
|
||||||
@@ -491,25 +440,8 @@ public static unsafe class AllocationManager
|
|||||||
#endif
|
#endif
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (ptr == null)
|
|
||||||
{
|
|
||||||
return Allocate(instance, newSize, alignment, allocationOption
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, pHandle
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var selfPtr = (FreeListAllocator*)instance;
|
var selfPtr = (FreeListAllocator*)instance;
|
||||||
var newPtr = selfPtr->_freeList.Allocate(newSize, alignment, allocationOption);
|
var newPtr = selfPtr->_freeList.Reallocate(ptr, oldSize, newSize, alignment, allocationOption);
|
||||||
if (newPtr == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
|
||||||
|
|
||||||
selfPtr->_freeList.Free(ptr);
|
|
||||||
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
#if MHP_ENABLE_SAFETY_CHECKS
|
||||||
RemoveAllocation(*pHandle);
|
RemoveAllocation(*pHandle);
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ public unsafe struct Arena : IMemoryAllocator<Arena, Arena.CreationOptions>
|
|||||||
[FieldOffset(16)]
|
[FieldOffset(16)]
|
||||||
private nuint _offset;
|
private nuint _offset;
|
||||||
|
|
||||||
|
public readonly byte* Buffer => _buffer;
|
||||||
|
public readonly nuint Size => _size;
|
||||||
|
public readonly nuint Offset => _offset;
|
||||||
|
|
||||||
public Arena(nuint size)
|
public Arena(nuint size)
|
||||||
{
|
{
|
||||||
ArgumentOutOfRangeException.ThrowIfNegative(size);
|
ArgumentOutOfRangeException.ThrowIfNegative(size);
|
||||||
@@ -55,7 +59,7 @@ public unsafe struct Arena : IMemoryAllocator<Arena, Arena.CreationOptions>
|
|||||||
{
|
{
|
||||||
if (_buffer == null)
|
if (_buffer == null)
|
||||||
{
|
{
|
||||||
throw new ObjectDisposedException(nameof(Arena));
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (size == 0)
|
if (size == 0)
|
||||||
@@ -92,6 +96,47 @@ public unsafe struct Arena : IMemoryAllocator<Arena, Arena.CreationOptions>
|
|||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void* Reallocate(void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
|
||||||
|
{
|
||||||
|
if (_buffer == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ptr == null)
|
||||||
|
{
|
||||||
|
return Allocate(newSize, alignment, allocationOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
var additionalSize = newSize - oldSize;
|
||||||
|
var currentOffset = Volatile.Read(ref _offset);
|
||||||
|
|
||||||
|
if ((byte*)ptr + oldSize == _buffer + currentOffset)
|
||||||
|
{
|
||||||
|
if (currentOffset + additionalSize <= _size)
|
||||||
|
{
|
||||||
|
if (Interlocked.CompareExchange(ref _offset, currentOffset + additionalSize, currentOffset) == currentOffset)
|
||||||
|
{
|
||||||
|
if (allocationOption.HasFlag(AllocationOption.Clear) && additionalSize > 0)
|
||||||
|
{
|
||||||
|
MemClear((byte*)ptr + oldSize, additionalSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var newPtr = Allocate(newSize, alignment, allocationOption);
|
||||||
|
if (newPtr == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
||||||
|
return newPtr;
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly void Free(void* ptr)
|
public readonly void Free(void* ptr)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ namespace Misaki.HighPerformance.LowLevel.Buffer;
|
|||||||
/// A dynamic memory management structure that automatically grows by creating linked arenas when more space is needed.
|
/// A dynamic memory management structure that automatically grows by creating linked arenas when more space is needed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[StructLayout(LayoutKind.Explicit, Size = 128)]
|
[StructLayout(LayoutKind.Explicit, Size = 128)]
|
||||||
public unsafe struct DynamicArena : IMemoryAllocator<DynamicArena, DynamicArena.CreateOptions>
|
public unsafe struct DynamicArena : IMemoryAllocator<DynamicArena, DynamicArena.CreationOptions>
|
||||||
{
|
{
|
||||||
public struct CreateOptions
|
public struct CreationOptions
|
||||||
{
|
{
|
||||||
public uint initialSize;
|
public uint initialSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DynamicArena Create(in CreateOptions options)
|
public static DynamicArena Create(in CreationOptions options)
|
||||||
{
|
{
|
||||||
return new DynamicArena(options.initialSize);
|
return new DynamicArena(options.initialSize);
|
||||||
}
|
}
|
||||||
@@ -134,6 +134,27 @@ public unsafe struct DynamicArena : IMemoryAllocator<DynamicArena, DynamicArena.
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void* Reallocate(void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
|
||||||
|
{
|
||||||
|
if (ptr == null)
|
||||||
|
{
|
||||||
|
return Allocate(newSize, alignment, allocationOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
var newPtr = Allocate(newSize, alignment, allocationOption);
|
||||||
|
if (newPtr == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPtr != ptr)
|
||||||
|
{
|
||||||
|
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPtr;
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly void Free(void* ptr)
|
public readonly void Free(void* ptr)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ namespace Misaki.HighPerformance.LowLevel.Buffer;
|
|||||||
/// A variable-size allocator that uses per-thread caches for the hot path and a remote-free queue for cross-thread deallocation.
|
/// A variable-size allocator that uses per-thread caches for the hot path and a remote-free queue for cross-thread deallocation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpts>
|
public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOptions>
|
||||||
{
|
{
|
||||||
public struct CreationOpts
|
public struct CreationOptions
|
||||||
{
|
{
|
||||||
public nuint alignment;
|
public nuint alignment;
|
||||||
public nuint chunkSize;
|
public nuint chunkSize;
|
||||||
public int maxConcurrencyLevel;
|
public int maxConcurrencyLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FreeList Create(in CreationOpts opts)
|
public static FreeList Create(in CreationOptions opts)
|
||||||
{
|
{
|
||||||
return new FreeList(opts.alignment, opts.chunkSize, opts.maxConcurrencyLevel);
|
return new FreeList(opts.alignment, opts.chunkSize, opts.maxConcurrencyLevel);
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpts
|
|||||||
}
|
}
|
||||||
|
|
||||||
private const int _MAX_BUCKETS = 16;
|
private const int _MAX_BUCKETS = 16;
|
||||||
private const int _DEFAULT_MAX_CONCURRENCY_LEVEL = 16;
|
private const int _DEFAULT_MAX_CONCURRENCY_LEVEL = 1;
|
||||||
private const int _OVERFLOW_CACHE_INDEX = 0;
|
private const int _OVERFLOW_CACHE_INDEX = 0;
|
||||||
private const nuint _MIN_BLOCK_SIZE = 16;
|
private const nuint _MIN_BLOCK_SIZE = 16;
|
||||||
private const nuint _DEFAULT_CHUNK_SIZE = 64 * 1024;
|
private const nuint _DEFAULT_CHUNK_SIZE = 64 * 1024;
|
||||||
@@ -506,7 +506,7 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpts
|
|||||||
{
|
{
|
||||||
if (_disposed != 0)
|
if (_disposed != 0)
|
||||||
{
|
{
|
||||||
throw new ObjectDisposedException(nameof(FreeList));
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (size == 0)
|
if (size == 0)
|
||||||
@@ -590,6 +590,24 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpts
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void* Reallocate(void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption = AllocationOption.None)
|
||||||
|
{
|
||||||
|
if (_disposed != 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newPtr = Allocate(newSize, alignment, allocationOption);
|
||||||
|
if (newPtr != null && ptr != null)
|
||||||
|
{
|
||||||
|
var copySize = Math.Min(oldSize, newSize);
|
||||||
|
MemCpy(newPtr, ptr, copySize);
|
||||||
|
Free(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPtr;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Frees a previously allocated memory block.
|
/// Frees a previously allocated memory block.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -123,5 +123,6 @@ public unsafe interface IMemoryAllocator<TSelf, TOpts> : IDisposable
|
|||||||
static abstract TSelf Create(in TOpts opts);
|
static abstract TSelf Create(in TOpts opts);
|
||||||
|
|
||||||
void* Allocate(nuint size, nuint alignment, AllocationOption option = AllocationOption.None);
|
void* Allocate(nuint size, nuint alignment, AllocationOption option = AllocationOption.None);
|
||||||
|
void* Reallocate(void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption = AllocationOption.None);
|
||||||
void Free(void* ptr);
|
void Free(void* ptr);
|
||||||
}
|
}
|
||||||
@@ -41,33 +41,7 @@ public unsafe struct MemoryPool<T, TOpts> : IDisposable
|
|||||||
#endif
|
#endif
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (ptr == null)
|
return ((T*)pAllocator)->Reallocate(ptr, oldSize, newSize, alignment, allocationOption);
|
||||||
{
|
|
||||||
return Allocate(pAllocator, newSize, alignment, allocationOption
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, pHandle
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var newPtr = Allocate(pAllocator, newSize, alignment, allocationOption
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, pHandle
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
if (newPtr == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
|
||||||
Free(pAllocator, ptr
|
|
||||||
#if MHP_ENABLE_SAFETY_CHECKS
|
|
||||||
, *pHandle
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
|
|
||||||
return newPtr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Free(void* pAllocator, void* ptr
|
private static void Free(void* pAllocator, void* ptr
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Diagnostics;
|
using System.Collections;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
@@ -8,14 +8,14 @@ namespace Misaki.HighPerformance.LowLevel.Buffer;
|
|||||||
/// blocks within a preallocated buffer.
|
/// blocks within a preallocated buffer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This is not a thread-safe implementation.</remarks>
|
/// <remarks>This is not a thread-safe implementation.</remarks>
|
||||||
public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
|
public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOptions>
|
||||||
{
|
{
|
||||||
public struct CreationOpts
|
public struct CreationOptions
|
||||||
{
|
{
|
||||||
public nuint size;
|
public nuint size;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Stack Create(in CreationOpts opts)
|
public static Stack Create(in CreationOptions opts)
|
||||||
{
|
{
|
||||||
return new Stack(opts.size);
|
return new Stack(opts.size);
|
||||||
}
|
}
|
||||||
@@ -57,12 +57,9 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
|
|||||||
private uint _activeScopeCount;
|
private uint _activeScopeCount;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
internal readonly byte* Buffer => _buffer;
|
public readonly byte* Buffer => _buffer;
|
||||||
internal nuint Offset
|
public readonly nuint Size => _size;
|
||||||
{
|
public readonly nuint Offset => _offset;
|
||||||
readonly get => _offset;
|
|
||||||
set => _offset = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the StackAllocator class with a buffer of the specified size.
|
/// Initializes a new instance of the StackAllocator class with a buffer of the specified size.
|
||||||
@@ -141,6 +138,44 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
|
|||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void* Reallocate(void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
|
||||||
|
{
|
||||||
|
if (_buffer == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ptr == null)
|
||||||
|
{
|
||||||
|
return Allocate(newSize, alignment, allocationOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldBase = _buffer + _offset - oldSize;
|
||||||
|
if (ptr == oldBase)
|
||||||
|
{
|
||||||
|
if (newSize > oldSize)
|
||||||
|
{
|
||||||
|
var diff = newSize - oldSize;
|
||||||
|
_offset += diff;
|
||||||
|
if (allocationOption.HasFlag(AllocationOption.Clear))
|
||||||
|
{
|
||||||
|
MemClear(_buffer + _offset - diff, diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newPtr = Allocate(newSize, alignment, allocationOption);
|
||||||
|
if (newPtr == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
||||||
|
return newPtr;
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly void Free(void* ptr)
|
public readonly void Free(void* ptr)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public unsafe struct VirtualArena : IMemoryAllocator<VirtualArena, VirtualArena.
|
|||||||
{
|
{
|
||||||
public nuint reserveCapacity;
|
public nuint reserveCapacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static VirtualArena Create(in CreationOptions opts)
|
public static VirtualArena Create(in CreationOptions opts)
|
||||||
{
|
{
|
||||||
return new VirtualArena(opts.reserveCapacity);
|
return new VirtualArena(opts.reserveCapacity);
|
||||||
@@ -24,7 +25,12 @@ public unsafe struct VirtualArena : IMemoryAllocator<VirtualArena, VirtualArena.
|
|||||||
private nuint _committedSize;
|
private nuint _committedSize;
|
||||||
private nuint _allocatedOffset;
|
private nuint _allocatedOffset;
|
||||||
|
|
||||||
private volatile int _allocationLock;
|
private int _allocationLock;
|
||||||
|
|
||||||
|
public readonly byte* Buffer => _baseAddress;
|
||||||
|
public readonly nuint Reserved => _reserveCapacity;
|
||||||
|
public readonly nuint Committed => _committedSize;
|
||||||
|
public readonly nuint Allocated => _allocatedOffset;
|
||||||
|
|
||||||
public VirtualArena(nuint reserveCapacity)
|
public VirtualArena(nuint reserveCapacity)
|
||||||
{
|
{
|
||||||
@@ -52,49 +58,32 @@ public unsafe struct VirtualArena : IMemoryAllocator<VirtualArena, VirtualArena.
|
|||||||
/// <returns>A pointer to the allocated memory block if the allocation succeeds, otherwise null.</returns>
|
/// <returns>A pointer to the allocated memory block if the allocation succeeds, otherwise null.</returns>
|
||||||
public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption)
|
public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption)
|
||||||
{
|
{
|
||||||
while (Interlocked.CompareExchange(ref _allocationLock, 1, 0) != 0)
|
if (_baseAddress == null || size == 0)
|
||||||
{
|
{
|
||||||
Thread.SpinWait(1);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void* ptr;
|
var spinWait = new SpinWait();
|
||||||
|
|
||||||
try
|
while (true)
|
||||||
{
|
{
|
||||||
|
var currentOffset = Volatile.Read(ref _allocatedOffset);
|
||||||
|
|
||||||
// Align the requested offset
|
// Align the requested offset
|
||||||
var alignedOffset = (_allocatedOffset + alignment - 1) & ~(alignment - 1);
|
var alignedOffset = (currentOffset + alignment - 1) & ~(alignment - 1);
|
||||||
var newAllocatedOffset = alignedOffset + size;
|
var newOffset = alignedOffset + size;
|
||||||
|
|
||||||
if (newAllocatedOffset > _reserveCapacity)
|
if (newOffset > _reserveCapacity)
|
||||||
{
|
{
|
||||||
return null; // Out of reserved space
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newAllocatedOffset > _committedSize)
|
if (newOffset <= Volatile.Read(ref _committedSize))
|
||||||
{
|
{
|
||||||
var sizeToCommit = newAllocatedOffset - _committedSize;
|
// Try to atomically claim this space.
|
||||||
|
if (Interlocked.CompareExchange(ref _allocatedOffset, newOffset, currentOffset) == currentOffset)
|
||||||
// Align the commit size to the 64KB OS Page Size
|
|
||||||
sizeToCommit = (sizeToCommit + _PAGE_SIZE - 1) & ~(_PAGE_SIZE - 1);
|
|
||||||
|
|
||||||
var commitAddress = _baseAddress + _committedSize;
|
|
||||||
var result = Mmap(commitAddress, sizeToCommit, VirtualAllocationFlags.Commit);
|
|
||||||
|
|
||||||
if (result == null)
|
|
||||||
{
|
{
|
||||||
return null; // Out of physical RAM
|
var ptr = _baseAddress + alignedOffset;
|
||||||
}
|
|
||||||
|
|
||||||
_committedSize += sizeToCommit;
|
|
||||||
}
|
|
||||||
|
|
||||||
ptr = _baseAddress + alignedOffset;
|
|
||||||
_allocatedOffset = newAllocatedOffset;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _allocationLock, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allocationOption.HasFlag(AllocationOption.Clear))
|
if (allocationOption.HasFlag(AllocationOption.Clear))
|
||||||
{
|
{
|
||||||
@@ -104,6 +93,129 @@ public unsafe struct VirtualArena : IMemoryAllocator<VirtualArena, VirtualArena.
|
|||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spinWait.SpinOnce();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lockWait = new SpinWait();
|
||||||
|
while (Interlocked.CompareExchange(ref _allocationLock, 1, 0) != 0)
|
||||||
|
{
|
||||||
|
lockWait.SpinOnce();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// DOUBLE-CHECK: Did another thread commit enough memory while we were waiting for the lock?
|
||||||
|
var currentCommitted = _committedSize;
|
||||||
|
if (newOffset > currentCommitted)
|
||||||
|
{
|
||||||
|
var sizeToCommit = newOffset - currentCommitted;
|
||||||
|
sizeToCommit = (sizeToCommit + _PAGE_SIZE - 1) & ~(_PAGE_SIZE - 1);
|
||||||
|
|
||||||
|
var commitAddress = _baseAddress + currentCommitted;
|
||||||
|
var result = Mmap(commitAddress, sizeToCommit, VirtualAllocationFlags.Commit);
|
||||||
|
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Volatile.Write(ref _committedSize, currentCommitted + sizeToCommit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Volatile.Write(ref _allocationLock, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We committed the memory (or realized someone else did).
|
||||||
|
// Loop back up to try the lock-free allocation again!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void* Reallocate(void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
|
||||||
|
{
|
||||||
|
if (_baseAddress == null || newSize == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newSize <= oldSize)
|
||||||
|
{
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ptr == null)
|
||||||
|
{
|
||||||
|
return Allocate(newSize, alignment, allocationOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
var additionalSize = newSize - oldSize;
|
||||||
|
var currentOffset = Volatile.Read(ref _allocatedOffset);
|
||||||
|
|
||||||
|
// Fast-path: Check if it's the last allocated block
|
||||||
|
if ((byte*)ptr + oldSize == _baseAddress + currentOffset)
|
||||||
|
{
|
||||||
|
var newOffset = currentOffset + additionalSize;
|
||||||
|
|
||||||
|
// Check if we need to commit more physical memory
|
||||||
|
if (newOffset > Volatile.Read(ref _committedSize))
|
||||||
|
{
|
||||||
|
var spinWait = new SpinWait();
|
||||||
|
while (Interlocked.CompareExchange(ref _allocationLock, 1, 0) != 0)
|
||||||
|
{
|
||||||
|
spinWait.SpinOnce();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// DOUBLE CHECK: Did another thread commit the memory while we waited?
|
||||||
|
var currentCommitted = _committedSize;
|
||||||
|
if (newOffset > currentCommitted)
|
||||||
|
{
|
||||||
|
var sizeToCommit = newOffset - currentCommitted;
|
||||||
|
sizeToCommit = (sizeToCommit + _PAGE_SIZE - 1) & ~(_PAGE_SIZE - 1);
|
||||||
|
|
||||||
|
var commitAddress = _baseAddress + currentCommitted;
|
||||||
|
var result = Mmap(commitAddress, sizeToCommit, VirtualAllocationFlags.Commit);
|
||||||
|
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
return null; // OOM or mapping failure
|
||||||
|
}
|
||||||
|
|
||||||
|
Volatile.Write(ref _committedSize, currentCommitted + sizeToCommit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Volatile.Write(ref _allocationLock, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to atomically extend the block
|
||||||
|
if (Interlocked.CompareExchange(ref _allocatedOffset, newOffset, currentOffset) == currentOffset)
|
||||||
|
{
|
||||||
|
// Safe to clear: we own the space between oldSize and newOffset
|
||||||
|
if (allocationOption.HasFlag(AllocationOption.Clear) && additionalSize > 0)
|
||||||
|
{
|
||||||
|
MemClear((byte*)ptr + oldSize, additionalSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var newPtr = Allocate(newSize, alignment, allocationOption);
|
||||||
|
if (newPtr == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemCpy(newPtr, ptr, oldSize);
|
||||||
|
return newPtr;
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly void Free(void* ptr)
|
public readonly void Free(void* ptr)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
|
||||||
public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.CreationOpts>
|
public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.CreationOptions>
|
||||||
{
|
{
|
||||||
private const nuint _PAGE_SIZE = 64 * 1024;
|
private const nuint _PAGE_SIZE = 64 * 1024;
|
||||||
|
|
||||||
public struct CreationOpts
|
public struct CreationOptions
|
||||||
{
|
{
|
||||||
public nuint reserveCapacity;
|
public nuint reserveCapacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static VirtualStack Create(in CreationOpts opts)
|
public static VirtualStack Create(in CreationOptions opts)
|
||||||
{
|
{
|
||||||
return new VirtualStack(opts.reserveCapacity);
|
return new VirtualStack(opts.reserveCapacity);
|
||||||
}
|
}
|
||||||
@@ -56,12 +55,10 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
|
|||||||
private uint _activeScopeCount;
|
private uint _activeScopeCount;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
internal readonly byte* Buffer => _baseAddress;
|
public readonly byte* Buffer => _baseAddress;
|
||||||
internal nuint Offset
|
public readonly nuint Reserved => _reserveCapacity;
|
||||||
{
|
public readonly nuint Committed => _committedSize;
|
||||||
readonly get => _allocatedOffset;
|
public readonly nuint Allocated => _allocatedOffset;
|
||||||
set => _allocatedOffset = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public VirtualStack(nuint reserveCapacity)
|
public VirtualStack(nuint reserveCapacity)
|
||||||
{
|
{
|
||||||
@@ -120,7 +117,7 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
|
|||||||
|
|
||||||
if (newAllocatedOffset > _reserveCapacity)
|
if (newAllocatedOffset > _reserveCapacity)
|
||||||
{
|
{
|
||||||
return null; // Out of reserved space
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newAllocatedOffset > _committedSize)
|
if (newAllocatedOffset > _committedSize)
|
||||||
@@ -152,6 +149,68 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
|
|||||||
return userPtr;
|
return userPtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void* Reallocate(void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
|
||||||
|
{
|
||||||
|
if (_baseAddress == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newSize < oldSize)
|
||||||
|
{
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ptr == null)
|
||||||
|
{
|
||||||
|
return Allocate(newSize, alignment, allocationOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((byte*)ptr + oldSize == _baseAddress + _allocatedOffset)
|
||||||
|
{
|
||||||
|
var diff = newSize - oldSize;
|
||||||
|
_allocatedOffset += diff;
|
||||||
|
|
||||||
|
if (_allocatedOffset > _committedSize)
|
||||||
|
{
|
||||||
|
var sizeToCommit = _allocatedOffset - _committedSize;
|
||||||
|
|
||||||
|
// Align the commit size to the 64KB OS Page Size
|
||||||
|
sizeToCommit = (sizeToCommit + _PAGE_SIZE - 1) & ~(_PAGE_SIZE - 1);
|
||||||
|
|
||||||
|
var commitAddress = _baseAddress + _committedSize;
|
||||||
|
var result = Mmap(commitAddress, sizeToCommit, VirtualAllocationFlags.Commit);
|
||||||
|
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_committedSize += sizeToCommit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allocationOption.HasFlag(AllocationOption.Clear))
|
||||||
|
{
|
||||||
|
MemClear(_baseAddress + _allocatedOffset - diff, diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var newPtr = Allocate(newSize, alignment, allocationOption);
|
||||||
|
if (newPtr == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
||||||
|
|
||||||
|
return newPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly void Free(void* ptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resets the internal offset to its initial position, keeping the committed physical memory intact for future reuse.
|
/// Resets the internal offset to its initial position, keeping the committed physical memory intact for future reuse.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -161,11 +220,6 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
|
|||||||
_allocatedOffset = 0;
|
_allocatedOffset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public readonly void Free(void* ptr)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_baseAddress != null)
|
if (_baseAddress != null)
|
||||||
|
|||||||
@@ -454,19 +454,19 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var copyFrom = Math.Min(_count - length, start + length);
|
var numToCopy = Math.Min(length, _count - (start + length));
|
||||||
|
var copyFrom = _count - numToCopy;
|
||||||
|
|
||||||
|
if (numToCopy > 0)
|
||||||
|
{
|
||||||
MemCpy(UnsafeUtility.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), start),
|
MemCpy(UnsafeUtility.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), start),
|
||||||
UnsafeUtility.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), copyFrom),
|
UnsafeUtility.ReadArrayElementUnsafe<T>(_array.GetUnsafePtr(), copyFrom),
|
||||||
(uint)((_count - copyFrom) * sizeof(T))
|
(uint)((_count - copyFrom) * sizeof(T)));
|
||||||
);
|
}
|
||||||
|
|
||||||
_count -= length;
|
_count -= length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes the element at the specified index by swapping it with the last element and reducing the collection
|
|
||||||
/// size.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">The zero-based index of the element to remove. Must be within the bounds of the collection.</param>
|
|
||||||
public void RemoveAtSwapBack(int index)
|
public void RemoveAtSwapBack(int index)
|
||||||
{
|
{
|
||||||
RemoveRangeSwapBack(index, 1);
|
RemoveRangeSwapBack(index, 1);
|
||||||
|
|||||||
@@ -1,42 +1,4 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using BenchmarkDotNet.Running;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.Test.Benchmark;
|
||||||
|
|
||||||
//BenchmarkRunner.Run<SPMDBenchmark>();
|
BenchmarkRunner.Run<SPMDBenchmark>();
|
||||||
|
|
||||||
var opts = new AllocationManagerInitOpts
|
|
||||||
{
|
|
||||||
ArenaCapacity = 1024 * 1024,
|
|
||||||
StackCapacity = 1024 * 1024,
|
|
||||||
FreeListConcurrencyLevel = 1
|
|
||||||
};
|
|
||||||
|
|
||||||
var pool = new MemoryPool<VirtualStack, VirtualStack.CreationOpts>(new VirtualStack.CreationOpts
|
|
||||||
{
|
|
||||||
reserveCapacity = 1024 * 1024
|
|
||||||
});
|
|
||||||
|
|
||||||
var arr = new UnsafeArray<int>(1000, pool.AllocationHandle);
|
|
||||||
|
|
||||||
Test(in pool);
|
|
||||||
arr.Dispose();
|
|
||||||
|
|
||||||
Console.WriteLine("Done.");
|
|
||||||
|
|
||||||
void Test(ref readonly MemoryPool<VirtualStack, VirtualStack.CreationOpts> pool)
|
|
||||||
{
|
|
||||||
using var arr = new UnsafeArray<int>(1000, pool.AllocationHandle);
|
|
||||||
if (true)
|
|
||||||
{
|
|
||||||
using var arr1 = new UnsafeArray<int>(1000, pool.AllocationHandle);
|
|
||||||
}
|
|
||||||
|
|
||||||
using var arr3 = Test2(in pool);
|
|
||||||
using var arr2 = new UnsafeArray<int>(1000, pool.AllocationHandle);
|
|
||||||
}
|
|
||||||
|
|
||||||
UnsafeArray<int> Test2(ref readonly MemoryPool<VirtualStack, VirtualStack.CreationOpts> pool)
|
|
||||||
{
|
|
||||||
using var arr = new UnsafeArray<int>(1000, pool.AllocationHandle);
|
|
||||||
var arr1 = new UnsafeArray<int>(1000, pool.AllocationHandle);
|
|
||||||
return arr1;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
|
||||||
|
namespace Misaki.HighPerformance.Test.UnitTest.Collections;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class TestFixedString
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void TestFixedString32()
|
||||||
|
{
|
||||||
|
var fs = new FixedString32("Hello");
|
||||||
|
Assert.AreEqual(5, fs.Length);
|
||||||
|
Assert.AreEqual("Hello", fs.ToString());
|
||||||
|
Assert.AreEqual("Hello", fs.Value);
|
||||||
|
|
||||||
|
fs.Value = "World";
|
||||||
|
Assert.AreEqual(5, fs.Length);
|
||||||
|
Assert.AreEqual("World", fs.Value);
|
||||||
|
|
||||||
|
fs.Value = "";
|
||||||
|
Assert.AreEqual(0, fs.Length);
|
||||||
|
Assert.AreEqual("", fs.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestFixedString32TooLong()
|
||||||
|
{
|
||||||
|
// MAX_LENGTH is 15
|
||||||
|
try
|
||||||
|
{
|
||||||
|
new FixedString32("This string is definitely longer than 15 characters");
|
||||||
|
Assert.Fail("Should have thrown ArgumentException");
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
// Success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestFixedString64()
|
||||||
|
{
|
||||||
|
var fs = new FixedString64("A bit longer string");
|
||||||
|
Assert.AreEqual("A bit longer string", fs.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestFixedString128()
|
||||||
|
{
|
||||||
|
var fs = new FixedString128("Even longer string for 128 bytes buffer");
|
||||||
|
Assert.AreEqual("Even longer string for 128 bytes buffer", fs.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
|
||||||
|
namespace Misaki.HighPerformance.Test.UnitTest.Collections;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class TestUnsafeHashMap
|
||||||
|
{
|
||||||
|
private UnsafeHashMap<int, int> _map;
|
||||||
|
|
||||||
|
[TestInitialize]
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
_map = new UnsafeHashMap<int, int>(4, Allocator.Persistent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCleanup]
|
||||||
|
public void Cleanup()
|
||||||
|
{
|
||||||
|
if (_map.IsCreated)
|
||||||
|
{
|
||||||
|
_map.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestAddGet()
|
||||||
|
{
|
||||||
|
_map.Add(1, 100);
|
||||||
|
_map.Add(2, 200);
|
||||||
|
Assert.AreEqual(2, _map.Count);
|
||||||
|
Assert.AreEqual(100, _map[1]);
|
||||||
|
Assert.AreEqual(200, _map[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestTryAdd()
|
||||||
|
{
|
||||||
|
Assert.IsTrue(_map.TryAdd(1, 100));
|
||||||
|
Assert.IsFalse(_map.TryAdd(1, 200)); // Duplicate key
|
||||||
|
Assert.AreEqual(100, _map[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestRemove()
|
||||||
|
{
|
||||||
|
_map.Add(1, 100);
|
||||||
|
Assert.IsTrue(_map.Remove(1));
|
||||||
|
Assert.IsFalse(_map.ContainsKey(1));
|
||||||
|
Assert.AreEqual(0, _map.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestTryGetValue()
|
||||||
|
{
|
||||||
|
_map.Add(1, 100);
|
||||||
|
Assert.IsTrue(_map.TryGetValue(1, out var value));
|
||||||
|
Assert.AreEqual(100, value);
|
||||||
|
Assert.IsFalse(_map.TryGetValue(2, out _));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestIndexerUpdate()
|
||||||
|
{
|
||||||
|
_map[1] = 100;
|
||||||
|
_map[1] = 200;
|
||||||
|
Assert.AreEqual(200, _map[1]);
|
||||||
|
Assert.AreEqual(1, _map.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestClear()
|
||||||
|
{
|
||||||
|
_map.Add(1, 100);
|
||||||
|
_map.Clear();
|
||||||
|
Assert.AreEqual(0, _map.Count);
|
||||||
|
Assert.IsFalse(_map.ContainsKey(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestEnumerator()
|
||||||
|
{
|
||||||
|
_map.Add(1, 10);
|
||||||
|
_map.Add(2, 20);
|
||||||
|
var keySum = 0;
|
||||||
|
var valueSum = 0;
|
||||||
|
foreach (var kvp in _map)
|
||||||
|
{
|
||||||
|
keySum += kvp.Key;
|
||||||
|
valueSum += kvp.Value;
|
||||||
|
}
|
||||||
|
Assert.AreEqual(3, keySum);
|
||||||
|
Assert.AreEqual(30, valueSum);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
|
||||||
|
namespace Misaki.HighPerformance.Test.UnitTest.Collections;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class TestUnsafeHashSet
|
||||||
|
{
|
||||||
|
private UnsafeHashSet<int> _set;
|
||||||
|
|
||||||
|
[TestInitialize]
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
_set = new UnsafeHashSet<int>(4, Allocator.Persistent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCleanup]
|
||||||
|
public void Cleanup()
|
||||||
|
{
|
||||||
|
if (_set.IsCreated)
|
||||||
|
{
|
||||||
|
_set.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestAddContains()
|
||||||
|
{
|
||||||
|
Assert.IsTrue(_set.Add(1));
|
||||||
|
Assert.IsTrue(_set.Add(2));
|
||||||
|
Assert.IsFalse(_set.Add(1)); // Duplicate
|
||||||
|
Assert.AreEqual(2, _set.Count);
|
||||||
|
Assert.IsTrue(_set.Contains(1));
|
||||||
|
Assert.IsTrue(_set.Contains(2));
|
||||||
|
Assert.IsFalse(_set.Contains(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestRemove()
|
||||||
|
{
|
||||||
|
_set.Add(1);
|
||||||
|
_set.Add(2);
|
||||||
|
Assert.IsTrue(_set.Remove(1));
|
||||||
|
Assert.IsFalse(_set.Contains(1));
|
||||||
|
Assert.AreEqual(1, _set.Count);
|
||||||
|
Assert.IsFalse(_set.Remove(3)); // Not present
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestClear()
|
||||||
|
{
|
||||||
|
_set.Add(1);
|
||||||
|
_set.Add(2);
|
||||||
|
_set.Clear();
|
||||||
|
Assert.AreEqual(0, _set.Count);
|
||||||
|
Assert.IsFalse(_set.Contains(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestResize()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
_set.Add(i);
|
||||||
|
}
|
||||||
|
Assert.AreEqual(100, _set.Count);
|
||||||
|
for (var i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
Assert.IsTrue(_set.Contains(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestEnumerator()
|
||||||
|
{
|
||||||
|
_set.Add(10);
|
||||||
|
_set.Add(20);
|
||||||
|
var sum = 0;
|
||||||
|
var count = 0;
|
||||||
|
foreach (var item in _set)
|
||||||
|
{
|
||||||
|
sum += item;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
Assert.AreEqual(30, sum);
|
||||||
|
Assert.AreEqual(2, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
|
||||||
|
namespace Misaki.HighPerformance.Test.UnitTest.Collections;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class TestUnsafeList
|
||||||
|
{
|
||||||
|
private UnsafeList<int> _list;
|
||||||
|
|
||||||
|
[TestInitialize]
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
_list = new UnsafeList<int>(4, Allocator.Persistent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCleanup]
|
||||||
|
public void Cleanup()
|
||||||
|
{
|
||||||
|
if (_list.IsCreated)
|
||||||
|
{
|
||||||
|
_list.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestAdd()
|
||||||
|
{
|
||||||
|
_list.Add(1);
|
||||||
|
_list.Add(2);
|
||||||
|
_list.Add(3);
|
||||||
|
Assert.AreEqual(3, _list.Count);
|
||||||
|
Assert.AreEqual(1, _list[0]);
|
||||||
|
Assert.AreEqual(2, _list[1]);
|
||||||
|
Assert.AreEqual(3, _list[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestResize()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
_list.Add(i);
|
||||||
|
}
|
||||||
|
Assert.AreEqual(10, _list.Count);
|
||||||
|
Assert.IsTrue(_list.Capacity >= 10);
|
||||||
|
for (var i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(i, _list[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestRemoveAt()
|
||||||
|
{
|
||||||
|
_list.Add(0);
|
||||||
|
_list.Add(1);
|
||||||
|
_list.Add(2);
|
||||||
|
_list.RemoveAt(1);
|
||||||
|
Assert.AreEqual(2, _list.Count);
|
||||||
|
Assert.AreEqual(0, _list[0]);
|
||||||
|
Assert.AreEqual(2, _list[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestRemoveAtSwapBack()
|
||||||
|
{
|
||||||
|
_list.Add(10);
|
||||||
|
_list.Add(11);
|
||||||
|
_list.Add(12);
|
||||||
|
_list.Add(13);
|
||||||
|
|
||||||
|
// Before: [10, 11, 12, 13]
|
||||||
|
_list.RemoveAtSwapBack(1);
|
||||||
|
// After: [10, 13, 12]
|
||||||
|
|
||||||
|
Assert.AreEqual(3, _list.Count, "Count should be 3");
|
||||||
|
Assert.AreEqual(10, _list[0], "Index 0 should be 10");
|
||||||
|
Assert.AreEqual(13, _list[1], "Index 1 should be 13");
|
||||||
|
Assert.AreEqual(12, _list[2], "Index 2 should be 12");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestClear()
|
||||||
|
{
|
||||||
|
_list.Add(1);
|
||||||
|
_list.Add(2);
|
||||||
|
_list.Clear();
|
||||||
|
Assert.AreEqual(0, _list.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestAddRange()
|
||||||
|
{
|
||||||
|
int[] values = { 10, 20, 30 };
|
||||||
|
_list.AddRange(values);
|
||||||
|
Assert.AreEqual(3, _list.Count);
|
||||||
|
Assert.AreEqual(10, _list[0]);
|
||||||
|
Assert.AreEqual(20, _list[1]);
|
||||||
|
Assert.AreEqual(30, _list[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestAsSpan()
|
||||||
|
{
|
||||||
|
_list.Add(1);
|
||||||
|
_list.Add(2);
|
||||||
|
var span = _list.AsSpan();
|
||||||
|
Assert.AreEqual(2, span.Length);
|
||||||
|
Assert.AreEqual(1, span[0]);
|
||||||
|
Assert.AreEqual(2, span[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestEnumerator()
|
||||||
|
{
|
||||||
|
_list.Add(1);
|
||||||
|
_list.Add(2);
|
||||||
|
_list.Add(3);
|
||||||
|
var sum = 0;
|
||||||
|
foreach (var item in _list)
|
||||||
|
{
|
||||||
|
sum += item;
|
||||||
|
}
|
||||||
|
Assert.AreEqual(6, sum);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Misaki.HighPerformance.Test.UnitTest.Collections;
|
namespace Misaki.HighPerformance.Test.UnitTest.Collections;
|
||||||
|
|
||||||
@@ -50,14 +47,14 @@ public class TestUnsafeQueue
|
|||||||
{
|
{
|
||||||
Assert.IsFalse(_queue.TryPeek(out _));
|
Assert.IsFalse(_queue.TryPeek(out _));
|
||||||
_queue.Enqueue(5);
|
_queue.Enqueue(5);
|
||||||
Assert.IsTrue(_queue.TryPeek(out int value));
|
Assert.IsTrue(_queue.TryPeek(out var value));
|
||||||
Assert.AreEqual(5, value);
|
Assert.AreEqual(5, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TestResize()
|
public void TestResize()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 10; i++)
|
for (var i = 0; i < 10; i++)
|
||||||
{
|
{
|
||||||
_queue.Enqueue(i);
|
_queue.Enqueue(i);
|
||||||
}
|
}
|
||||||
@@ -67,12 +64,12 @@ public class TestUnsafeQueue
|
|||||||
_queue.Dequeue();
|
_queue.Dequeue();
|
||||||
_queue.Dequeue();
|
_queue.Dequeue();
|
||||||
|
|
||||||
for (int i = 10; i < 20; i++)
|
for (var i = 10; i < 20; i++)
|
||||||
{
|
{
|
||||||
_queue.Enqueue(i);
|
_queue.Enqueue(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 2; i < 20; i++)
|
for (var i = 2; i < 20; i++)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(i, _queue.Dequeue());
|
Assert.AreEqual(i, _queue.Dequeue());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user