feat(allocator): add VirtualArena and FreeList allocators
Introduce VirtualArena for large, thread-safe virtual memory allocation and FreeList allocator for efficient persistent allocations. Update AllocationManager to support new allocators, add cross-platform virtual memory utilities, and improve thread-safety and performance in existing allocators. Bump version to 1.5.0 and update project configuration. BREAKING CHANGE: AllocationManager initialization now requires explicit parameters for arena and FreeList capacities. Existing allocator usage may require code changes.
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
using Misaki.HighPerformance.Collections;
|
using Misaki.HighPerformance.Collections;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
|
||||||
@@ -49,16 +48,16 @@ public static unsafe class AllocationManager
|
|||||||
{
|
{
|
||||||
private const int _ARENA_MAGIC_ID = -3941029;
|
private const int _ARENA_MAGIC_ID = -3941029;
|
||||||
|
|
||||||
private DynamicArena _arena;
|
private VirtualArena _arena;
|
||||||
private AllocationHandle _handle;
|
|
||||||
private int _currentTick;
|
private int _currentTick;
|
||||||
|
private AllocationHandle _handle;
|
||||||
|
|
||||||
public readonly AllocationHandle Handle => _handle;
|
public readonly AllocationHandle Handle => _handle;
|
||||||
public readonly int CurrentTick => _currentTick;
|
public readonly int CurrentTick => _currentTick;
|
||||||
|
|
||||||
public void Init(uint initialSize)
|
public void Init(nuint capacity)
|
||||||
{
|
{
|
||||||
_arena = new DynamicArena(initialSize);
|
_arena = new VirtualArena(capacity);
|
||||||
_handle = new AllocationHandle
|
_handle = new AllocationHandle
|
||||||
{
|
{
|
||||||
State = Unsafe.AsPointer(ref this),
|
State = Unsafe.AsPointer(ref this),
|
||||||
@@ -67,6 +66,7 @@ public static unsafe class AllocationManager
|
|||||||
Free = null,
|
Free = null,
|
||||||
IsValid = &IsValid
|
IsValid = &IsValid
|
||||||
};
|
};
|
||||||
|
|
||||||
_currentTick = 0;
|
_currentTick = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,20 +265,94 @@ public static unsafe class AllocationManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const uint _DEFAULT_MEMORY_POOL_SIZE = 1024 * 1024; // 1 MB
|
private struct FreeListAllocator : IAllocator, IDisposable
|
||||||
|
{
|
||||||
|
private FreeList _freeList;
|
||||||
|
private AllocationHandle _handle;
|
||||||
|
|
||||||
private static readonly ArenaAllocator* s_pArenaAllocator;
|
public readonly AllocationHandle Handle => _handle;
|
||||||
private static readonly HeapAllocator* s_pHeapAllocator;
|
|
||||||
private static readonly StackAllocator* s_pStackAllocator;
|
|
||||||
|
|
||||||
private static bool s_disposed;
|
public void Init(int concurrencyLevel)
|
||||||
|
{
|
||||||
|
_freeList = new FreeList(8, 64 * 1024, concurrencyLevel);
|
||||||
|
_handle = new AllocationHandle
|
||||||
|
{
|
||||||
|
State = Unsafe.AsPointer(ref this),
|
||||||
|
Alloc = &Allocate,
|
||||||
|
Realloc = &Reallocate,
|
||||||
|
Free = &Free,
|
||||||
|
IsValid = &IsValid
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
||||||
|
{
|
||||||
|
var selfPtr = (FreeListAllocator*)instance;
|
||||||
|
var ptr = selfPtr->_freeList.Allocate(size, alignment, allocationOption);
|
||||||
|
if (ptr == null)
|
||||||
|
{
|
||||||
|
*pHandle = MemoryHandle.Invalid;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
*pHandle = AddAllocation(ptr);
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
||||||
|
{
|
||||||
|
if (ptr == null)
|
||||||
|
{
|
||||||
|
return Allocate(instance, newSize, alignment, allocationOption, pHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
var selfPtr = (FreeListAllocator*)instance;
|
||||||
|
var newPtr = selfPtr->_freeList.Allocate(newSize, alignment, allocationOption);
|
||||||
|
if (newPtr == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
||||||
|
|
||||||
|
selfPtr->_freeList.Free(ptr);
|
||||||
|
RemoveAllocation(*pHandle);
|
||||||
|
|
||||||
|
*pHandle = AddAllocation(newPtr);
|
||||||
|
return newPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsValid(void* instance, MemoryHandle handle)
|
||||||
|
{
|
||||||
|
return ContainsAllocation(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Free(void* instance, void* ptr, MemoryHandle handle)
|
||||||
|
{
|
||||||
|
var selfPtr = (FreeListAllocator*)instance;
|
||||||
|
selfPtr->_freeList.Free(ptr);
|
||||||
|
RemoveAllocation(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_freeList.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ArenaAllocator* s_pArenaAllocator;
|
||||||
|
private static HeapAllocator* s_pHeapAllocator;
|
||||||
|
private static StackAllocator* s_pStackAllocator;
|
||||||
|
private static FreeListAllocator* s_pFreeListAllocator;
|
||||||
|
|
||||||
|
private static bool s_initialized;
|
||||||
|
|
||||||
#if ENABLE_DEBUG_LAYER
|
#if ENABLE_DEBUG_LAYER
|
||||||
private static SpinLock s_liveLock;
|
private static SpinLock s_liveLock;
|
||||||
private static AllocationHeader* s_pLiveHead;
|
private static AllocationHeader* s_pLiveHead;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private static readonly ConcurrentSlotMap<IntPtr> s_allocations;
|
private static ConcurrentSlotMap<IntPtr> s_allocations = null!;
|
||||||
|
|
||||||
public static readonly MemoryHandle MagicHandle = new MemoryHandle(int.MinValue, int.MinValue);
|
public static readonly MemoryHandle MagicHandle = new MemoryHandle(int.MinValue, int.MinValue);
|
||||||
|
|
||||||
@@ -287,14 +361,12 @@ public static unsafe class AllocationManager
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static int LiveAllocationCount => s_allocations.Count;
|
public static int LiveAllocationCount => s_allocations.Count;
|
||||||
|
|
||||||
static AllocationManager()
|
public static void Initialize(nuint arenaCapacity, int freeListConcurrencyLevel)
|
||||||
{
|
{
|
||||||
var allocatorTotalSize = (nuint)(sizeof(ArenaAllocator) + sizeof(HeapAllocator) + sizeof(StackAllocator));
|
if (s_initialized)
|
||||||
var basePtr = Malloc(allocatorTotalSize);
|
{
|
||||||
|
return;
|
||||||
s_pArenaAllocator = (ArenaAllocator*)basePtr;
|
}
|
||||||
s_pHeapAllocator = (HeapAllocator*)((byte*)basePtr + (nuint)sizeof(ArenaAllocator));
|
|
||||||
s_pStackAllocator = (StackAllocator*)((byte*)basePtr + (nuint)(sizeof(ArenaAllocator) + sizeof(HeapAllocator)));
|
|
||||||
|
|
||||||
#if ENABLE_DEBUG_LAYER
|
#if ENABLE_DEBUG_LAYER
|
||||||
s_liveLock = new SpinLock(false);
|
s_liveLock = new SpinLock(false);
|
||||||
@@ -303,9 +375,19 @@ public static unsafe class AllocationManager
|
|||||||
|
|
||||||
s_allocations = new ConcurrentSlotMap<IntPtr>(256);
|
s_allocations = new ConcurrentSlotMap<IntPtr>(256);
|
||||||
|
|
||||||
s_pArenaAllocator->Init(_DEFAULT_MEMORY_POOL_SIZE);
|
var ptr = (byte*)Malloc((nuint)(sizeof(ArenaAllocator) + sizeof(HeapAllocator) + sizeof(StackAllocator) + sizeof(FreeListAllocator)));
|
||||||
|
|
||||||
|
s_pArenaAllocator = (ArenaAllocator*)ptr;
|
||||||
|
s_pHeapAllocator = (HeapAllocator*)(ptr + sizeof(ArenaAllocator));
|
||||||
|
s_pStackAllocator = (StackAllocator*)(ptr + sizeof(ArenaAllocator) + sizeof(HeapAllocator));
|
||||||
|
s_pFreeListAllocator = (FreeListAllocator*)(ptr + sizeof(ArenaAllocator) + sizeof(HeapAllocator) + sizeof(StackAllocator));
|
||||||
|
|
||||||
|
s_pArenaAllocator->Init(arenaCapacity);
|
||||||
s_pHeapAllocator->Init();
|
s_pHeapAllocator->Init();
|
||||||
s_pStackAllocator->Init();
|
s_pStackAllocator->Init();
|
||||||
|
s_pFreeListAllocator->Init(freeListConcurrencyLevel);
|
||||||
|
|
||||||
|
s_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if ENABLE_DEBUG_LAYER
|
#if ENABLE_DEBUG_LAYER
|
||||||
@@ -472,10 +554,13 @@ public static unsafe class AllocationManager
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static AllocationHandle GetAllocationHandle(Allocator allocator)
|
public static AllocationHandle GetAllocationHandle(Allocator allocator)
|
||||||
{
|
{
|
||||||
|
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
|
||||||
|
|
||||||
return allocator switch
|
return allocator switch
|
||||||
{
|
{
|
||||||
Allocator.Temp => s_pArenaAllocator->Handle,
|
Allocator.Temp => s_pArenaAllocator->Handle,
|
||||||
Allocator.Persistent => s_pHeapAllocator->Handle,
|
Allocator.Persistent => s_pHeapAllocator->Handle,
|
||||||
|
Allocator.FreeList => s_pFreeListAllocator->Handle,
|
||||||
_ => throw new ArgumentException("Target allocator type does not support custom allocation.", nameof(allocator)),
|
_ => throw new ArgumentException("Target allocator type does not support custom allocation.", nameof(allocator)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -491,6 +576,8 @@ public static unsafe class AllocationManager
|
|||||||
/// <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, MemoryHandle* pHandle)
|
public static void* HeapAlloc(nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
||||||
{
|
{
|
||||||
|
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
|
||||||
|
|
||||||
#if ENABLE_DEBUG_LAYER
|
#if ENABLE_DEBUG_LAYER
|
||||||
var ptr = DebugAllocate(size, alignment);
|
var ptr = DebugAllocate(size, alignment);
|
||||||
#else
|
#else
|
||||||
@@ -508,7 +595,7 @@ public static unsafe class AllocationManager
|
|||||||
MemClear(ptr, size);
|
MemClear(ptr, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
*pHandle = AddAllocation((IntPtr)ptr);
|
*pHandle = AddAllocation(ptr);
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -520,6 +607,8 @@ public static unsafe class AllocationManager
|
|||||||
/// <param name="handle">The handle representing the memory allocation to free. The handle must be valid and previously allocated.</param>
|
/// <param name="handle">The handle representing the memory allocation to free. The handle must be valid and previously allocated.</param>
|
||||||
public static void HeapFree(void* ptr, MemoryHandle handle)
|
public static void HeapFree(void* ptr, MemoryHandle handle)
|
||||||
{
|
{
|
||||||
|
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
|
||||||
|
|
||||||
#if ENABLE_DEBUG_LAYER
|
#if ENABLE_DEBUG_LAYER
|
||||||
if (handle != MagicHandle)
|
if (handle != MagicHandle)
|
||||||
{
|
{
|
||||||
@@ -540,6 +629,8 @@ public static unsafe class AllocationManager
|
|||||||
/// <param name="handle">The handle representing the memory allocation to free. The handle must be valid and previously allocated.</param>
|
/// <param name="handle">The handle representing the memory allocation to free. The handle must be valid and previously allocated.</param>
|
||||||
public static void HeapFree(MemoryHandle handle)
|
public static void HeapFree(MemoryHandle handle)
|
||||||
{
|
{
|
||||||
|
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
|
||||||
|
|
||||||
if (TryGetAllocation(handle, out var ptr))
|
if (TryGetAllocation(handle, out var ptr))
|
||||||
{
|
{
|
||||||
HeapFree((void*)ptr, handle);
|
HeapFree((void*)ptr, handle);
|
||||||
@@ -552,6 +643,7 @@ public static unsafe class AllocationManager
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void ResetTempAllocator()
|
public static void ResetTempAllocator()
|
||||||
{
|
{
|
||||||
|
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
|
||||||
s_pArenaAllocator->Reset();
|
s_pArenaAllocator->Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,6 +654,7 @@ public static unsafe class AllocationManager
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static Stack.Scope CreateStackScope()
|
public static Stack.Scope CreateStackScope()
|
||||||
{
|
{
|
||||||
|
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
|
||||||
return StackAllocator.CreateScope(s_pStackAllocator);
|
return StackAllocator.CreateScope(s_pStackAllocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -571,9 +664,11 @@ public static unsafe class AllocationManager
|
|||||||
/// <param name="ptr">A pointer to the memory block to be registered. The pointer must reference a valid, allocated memory region.</param>
|
/// <param name="ptr">A pointer to the memory block to be registered. The pointer must reference a valid, allocated memory region.</param>
|
||||||
/// <returns>A MemoryHandle representing the registered allocation.</returns>
|
/// <returns>A MemoryHandle representing the registered allocation.</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static MemoryHandle AddAllocation(IntPtr ptr)
|
public static MemoryHandle AddAllocation(void* ptr)
|
||||||
{
|
{
|
||||||
var id = s_allocations.Add(ptr, out var generation);
|
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
|
||||||
|
|
||||||
|
var id = s_allocations.Add((nint)ptr, out var generation);
|
||||||
return new MemoryHandle(id, generation);
|
return new MemoryHandle(id, generation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,6 +680,7 @@ public static unsafe class AllocationManager
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static bool RemoveAllocation(MemoryHandle handle)
|
public static bool RemoveAllocation(MemoryHandle handle)
|
||||||
{
|
{
|
||||||
|
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
|
||||||
return s_allocations.Remove(handle.id, handle.generation);
|
return s_allocations.Remove(handle.id, handle.generation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,6 +693,7 @@ public static unsafe class AllocationManager
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static bool TryGetAllocation(MemoryHandle handle, out IntPtr ptr)
|
public static bool TryGetAllocation(MemoryHandle handle, out IntPtr ptr)
|
||||||
{
|
{
|
||||||
|
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
|
||||||
return s_allocations.TryGetElement(handle.id, handle.generation, out ptr);
|
return s_allocations.TryGetElement(handle.id, handle.generation, out ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -612,6 +709,8 @@ public static unsafe class AllocationManager
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static bool ContainsAllocation(MemoryHandle handle)
|
public static bool ContainsAllocation(MemoryHandle handle)
|
||||||
{
|
{
|
||||||
|
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
|
||||||
|
|
||||||
if (handle == MagicHandle)
|
if (handle == MagicHandle)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
@@ -625,7 +724,7 @@ public static unsafe class AllocationManager
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static void Dispose()
|
public static void Dispose()
|
||||||
{
|
{
|
||||||
if (s_disposed)
|
if (!s_initialized)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -678,15 +777,12 @@ public static unsafe class AllocationManager
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// NOTE: Arena allocator holds the base ptr for all allocators, heap and stack allocators do not own any memory themselves.
|
s_pArenaAllocator->Dispose();
|
||||||
if (s_pArenaAllocator != null)
|
s_pStackAllocator->Dispose();
|
||||||
{
|
s_pFreeListAllocator->Dispose();
|
||||||
s_pArenaAllocator->Dispose();
|
|
||||||
s_pStackAllocator->Dispose();
|
|
||||||
|
|
||||||
NativeMemory.Free(s_pArenaAllocator);
|
Free(s_pArenaAllocator);
|
||||||
}
|
|
||||||
|
|
||||||
s_disposed = true;
|
s_initialized = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,4 +25,8 @@ public enum Allocator : byte
|
|||||||
/// Allocator for persistent allocations. Allocations are not automatically released after use.
|
/// Allocator for persistent allocations. Allocations are not automatically released after use.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Persistent,
|
Persistent,
|
||||||
|
/// <summary>
|
||||||
|
/// Allocator for persistent allocations using a free list. Allocations are not automatically released after use, but can be reused to reduce fragmentation and improve performance.
|
||||||
|
/// </summary>
|
||||||
|
FreeList
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
@@ -30,10 +31,11 @@ public unsafe struct Arena : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allocates a block of memory of a specified size with a given alignment. Returns a pointer to the allocated
|
/// Allocates a block of memory of a specified size with a given alignment.
|
||||||
/// memory or null if allocation fails.
|
|
||||||
/// You don't need to free the memory manually, it will be freed when the arena is disposed.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is thread safe.
|
||||||
|
/// </remarks>
|
||||||
/// <param name="size">Specifies the amount of memory to allocate in bytes.</param>
|
/// <param name="size">Specifies the amount of memory to allocate in bytes.</param>
|
||||||
/// <param name="alignment">Defines the alignment requirement for the allocated memory.</param>
|
/// <param name="alignment">Defines the alignment requirement for the allocated memory.</param>
|
||||||
/// <param name="allocationOption">The option when allocating memory.</param>
|
/// <param name="allocationOption">The option when allocating memory.</param>
|
||||||
@@ -81,17 +83,11 @@ public unsafe struct Arena : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resets the arena, optionally clearing the allocated memory.
|
/// Resets the arena.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="clear">If true, the allocated memory will be cleared; otherwise, it will not be cleared.</param>
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
/// <exception cref="ObjectDisposedException">Thrown if the arena has been disposed.</exception>
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
if (_buffer == null)
|
|
||||||
{
|
|
||||||
throw new ObjectDisposedException(nameof(DynamicArena));
|
|
||||||
}
|
|
||||||
|
|
||||||
_offset = 0;
|
_offset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ using System.Runtime.InteropServices;
|
|||||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A dynamic memory management structure that automatically grows by creating linked arenas
|
/// A dynamic memory management structure that automatically grows by creating linked arenas when more space is needed.
|
||||||
/// when more space is needed.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[StructLayout(LayoutKind.Explicit, Size = 128)]
|
[StructLayout(LayoutKind.Explicit, Size = 128)]
|
||||||
public unsafe struct DynamicArena : IDisposable
|
public unsafe struct DynamicArena : IDisposable
|
||||||
@@ -97,6 +96,9 @@ public unsafe struct DynamicArena : IDisposable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allocates a block of memory with specified size and alignment. Creates a new arena if current one is full.
|
/// Allocates a block of memory with specified size and alignment. Creates a new arena if current one is full.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is thread safe.
|
||||||
|
/// </remarks>
|
||||||
/// <param name="size">Size of the memory block to allocate in bytes.</param>
|
/// <param name="size">Size of the memory block to allocate in bytes.</param>
|
||||||
/// <param name="alignment">Alignment requirement for the memory block.</param>
|
/// <param name="alignment">Alignment requirement for the memory block.</param>
|
||||||
/// <returns>Pointer to the allocated memory block.</returns>
|
/// <returns>Pointer to the allocated memory block.</returns>
|
||||||
@@ -132,10 +134,8 @@ public unsafe struct DynamicArena : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resets all arenas in the chain, optionally clearing their memory.
|
/// Resets all arenas in the chain.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="clear">If true, memory will be cleared during reset.</param>
|
|
||||||
/// <exception cref="ObjectDisposedException">Thrown if the arena has been disposed.</exception>
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
var current = _root;
|
var current = _root;
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ using System.Runtime.InteropServices;
|
|||||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A thread-safe variable-size allocator that uses per-thread caches for the hot path and
|
/// A variable-size allocator that uses per-thread caches for the hot path and a remote-free queue for cross-thread deallocation.
|
||||||
/// a remote-free queue for cross-thread deallocation.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public unsafe struct FreeList : IDisposable
|
public unsafe struct FreeList : IDisposable
|
||||||
@@ -94,11 +93,6 @@ public unsafe struct FreeList : IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly nuint Alignment => _alignment;
|
public readonly nuint Alignment => _alignment;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets whether the allocator has been disposed.
|
|
||||||
/// </summary>
|
|
||||||
public readonly bool IsDisposed => _disposed != 0;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the chunk size used by this allocator.
|
/// Gets the chunk size used by this allocator.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -208,7 +202,7 @@ public unsafe struct FreeList : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private readonly int FindBucket(nuint size)
|
private static int FindBucket(nuint size)
|
||||||
{
|
{
|
||||||
var blockSize = _MIN_BLOCK_SIZE;
|
var blockSize = _MIN_BLOCK_SIZE;
|
||||||
for (var i = 0; i < _MAX_BUCKETS; i++)
|
for (var i = 0; i < _MAX_BUCKETS; i++)
|
||||||
@@ -240,7 +234,7 @@ public unsafe struct FreeList : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void DrainRemoteFrees(ThreadCache* cache)
|
private readonly void DrainRemoteFrees(ThreadCache* cache)
|
||||||
{
|
{
|
||||||
var head = (FreeNode*)Interlocked.Exchange(ref cache->remoteFreeHead, 0);
|
var head = (FreeNode*)Interlocked.Exchange(ref cache->remoteFreeHead, 0);
|
||||||
while (head != null)
|
while (head != null)
|
||||||
@@ -320,7 +314,7 @@ public unsafe struct FreeList : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void* TryPopFromBucket(ThreadCache* cache, int cacheIndex, int bucketIndex)
|
private readonly void* TryPopFromBucket(ThreadCache* cache, int cacheIndex, int bucketIndex)
|
||||||
{
|
{
|
||||||
var buckets = GetBuckets(cache);
|
var buckets = GetBuckets(cache);
|
||||||
var bucket = &buckets[bucketIndex];
|
var bucket = &buckets[bucketIndex];
|
||||||
@@ -338,7 +332,7 @@ public unsafe struct FreeList : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void PushToBucket(ThreadCache* cache, int bucketIndex, void* ptr, MemoryChunk* ownerChunk, nuint blockSize)
|
private readonly void PushToBucket(ThreadCache* cache, int bucketIndex, void* ptr, MemoryChunk* ownerChunk, nuint blockSize)
|
||||||
{
|
{
|
||||||
var buckets = GetBuckets(cache);
|
var buckets = GetBuckets(cache);
|
||||||
var bucket = &buckets[bucketIndex];
|
var bucket = &buckets[bucketIndex];
|
||||||
@@ -490,6 +484,9 @@ public unsafe struct FreeList : IDisposable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Allocates a memory block of the specified size.
|
/// Allocates a memory block of the specified size.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is thread safe.
|
||||||
|
/// </remarks>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption = AllocationOption.None)
|
public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption = AllocationOption.None)
|
||||||
{
|
{
|
||||||
@@ -548,6 +545,7 @@ public unsafe struct FreeList : IDisposable
|
|||||||
{
|
{
|
||||||
ptr = AllocateFromChunk(cacheIndex, totalSize, alignment);
|
ptr = AllocateFromChunk(cacheIndex, totalSize, alignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ptr == null)
|
if (ptr == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
@@ -576,6 +574,9 @@ public unsafe struct FreeList : IDisposable
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Frees a previously allocated memory block.
|
/// Frees a previously allocated memory block.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is thread safe.
|
||||||
|
/// </remarks>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Free(void* ptr)
|
public void Free(void* ptr)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -221,6 +221,11 @@ public unsafe partial struct Stack : IDisposable
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
if (_buffer == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Free(_buffer);
|
Free(_buffer);
|
||||||
|
|
||||||
_buffer = null;
|
_buffer = null;
|
||||||
|
|||||||
119
Misaki.HighPerformance.LowLevel/Buffer/VirtualArena.cs
Normal file
119
Misaki.HighPerformance.LowLevel/Buffer/VirtualArena.cs
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A thread-safe memory management structure that reserves a large virtual address space and commits physical memory on demand as allocations are made.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe struct VirtualArena
|
||||||
|
{
|
||||||
|
private const nuint _PAGE_SIZE = 64 * 1024;
|
||||||
|
|
||||||
|
private byte* _baseAddress;
|
||||||
|
private nuint _reserveCapacity;
|
||||||
|
private nuint _committedSize;
|
||||||
|
private nuint _allocatedOffset;
|
||||||
|
|
||||||
|
private volatile int _allocationLock;
|
||||||
|
|
||||||
|
public VirtualArena(nuint reserveCapacity)
|
||||||
|
{
|
||||||
|
_reserveCapacity = (reserveCapacity + _PAGE_SIZE - 1) & ~(_PAGE_SIZE - 1);
|
||||||
|
_committedSize = 0;
|
||||||
|
_allocatedOffset = 0;
|
||||||
|
|
||||||
|
_baseAddress = (byte*)Mmap(null, _reserveCapacity, VirtualAllocationFlags.Reserve);
|
||||||
|
|
||||||
|
if (_baseAddress == null)
|
||||||
|
{
|
||||||
|
throw new OutOfMemoryException("Failed to reserve virtual address space.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allocates a block of memory of the specified size and alignment, using the given allocation options.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is thread safe.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="size">The number of bytes to allocate. Must be greater than zero and less than or equal to the reserved capacity.</param>
|
||||||
|
/// <param name="alignment">The alignment, in bytes, for the allocated memory block. Must be a power of two.</param>
|
||||||
|
/// <param name="allocationOption">The allocation options that control allocation behavior.</param>
|
||||||
|
/// <returns>A pointer to the allocated memory block if the allocation succeeds, otherwise null.</returns>
|
||||||
|
public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption)
|
||||||
|
{
|
||||||
|
while (Interlocked.CompareExchange(ref _allocationLock, 1, 0) != 0)
|
||||||
|
{
|
||||||
|
Thread.SpinWait(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* ptr;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Align the requested offset
|
||||||
|
var alignedOffset = (_allocatedOffset + alignment - 1) & ~(alignment - 1);
|
||||||
|
var newAllocatedOffset = alignedOffset + size;
|
||||||
|
|
||||||
|
if (newAllocatedOffset > _reserveCapacity)
|
||||||
|
{
|
||||||
|
return null; // Out of reserved space
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newAllocatedOffset > _committedSize)
|
||||||
|
{
|
||||||
|
var sizeToCommit = newAllocatedOffset - _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; // Out of physical RAM
|
||||||
|
}
|
||||||
|
|
||||||
|
_committedSize += sizeToCommit;
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr = _baseAddress + alignedOffset;
|
||||||
|
_allocatedOffset = newAllocatedOffset;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref _allocationLock, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allocationOption.HasFlag(AllocationOption.Clear))
|
||||||
|
{
|
||||||
|
MemClear(ptr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the arena.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_allocatedOffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_baseAddress != null)
|
||||||
|
{
|
||||||
|
Munmap(_baseAddress, _reserveCapacity);
|
||||||
|
|
||||||
|
_baseAddress = null;
|
||||||
|
_reserveCapacity = 0;
|
||||||
|
_committedSize = 0;
|
||||||
|
_allocatedOffset = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -206,11 +206,15 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
[Conditional("ENABLE_COLLECTION_CHECKS")]
|
||||||
private readonly void CheckNoResizeCapacity(int count)
|
private readonly void CheckNoResizeCapacity(int count)
|
||||||
{
|
{
|
||||||
CheckNoResizeCapacity(count, Count);
|
CheckNoResizeCapacity(count, Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
[Conditional("ENABLE_COLLECTION_CHECKS")]
|
||||||
private readonly void CheckNoResizeCapacity(int index, int count)
|
private readonly void CheckNoResizeCapacity(int index, int count)
|
||||||
{
|
{
|
||||||
if (index + count > Capacity)
|
if (index + count > Capacity)
|
||||||
@@ -242,16 +246,19 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public Enumerator GetEnumerator()
|
public Enumerator GetEnumerator()
|
||||||
{
|
{
|
||||||
return new((UnsafeList<T>*)UnsafeUtility.AddressOf(ref this));
|
return new((UnsafeList<T>*)UnsafeUtility.AddressOf(ref this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||||
{
|
{
|
||||||
return GetEnumerator();
|
return GetEnumerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
{
|
{
|
||||||
return GetEnumerator();
|
return GetEnumerator();
|
||||||
@@ -266,6 +273,7 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
|
|||||||
/// Otherwise the parallel reader will be invalid after the stack frame that creates the list is popped, even if the list's internal array is still valid.
|
/// Otherwise the parallel reader will be invalid after the stack frame that creates the list is popped, even if the list's internal array is still valid.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <returns>A <see cref="ParallelReader"/> instance that can be used to read items from the list in a thread-safe manner.</returns>
|
/// <returns>A <see cref="ParallelReader"/> instance that can be used to read items from the list in a thread-safe manner.</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public ParallelReader AsParallelReader()
|
public ParallelReader AsParallelReader()
|
||||||
{
|
{
|
||||||
return new((UnsafeList<T>*)UnsafeUtility.AddressOf(ref this));
|
return new((UnsafeList<T>*)UnsafeUtility.AddressOf(ref this));
|
||||||
@@ -280,6 +288,7 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
|
|||||||
/// Otherwise the parallel writer will be invalid after the stack frame that creates the list is popped, even if the list's internal array is still valid.
|
/// Otherwise the parallel writer will be invalid after the stack frame that creates the list is popped, even if the list's internal array is still valid.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <returns>A <see cref="ParallelWriter"/> instance that can be used to add items to the list in a thread-safe manner.</returns>
|
/// <returns>A <see cref="ParallelWriter"/> instance that can be used to add items to the list in a thread-safe manner.</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public ParallelWriter AsParallelWriter()
|
public ParallelWriter AsParallelWriter()
|
||||||
{
|
{
|
||||||
return new((UnsafeList<T>*)UnsafeUtility.AddressOf(ref this));
|
return new((UnsafeList<T>*)UnsafeUtility.AddressOf(ref this));
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
<Authors>Misaki</Authors>
|
<Authors>Misaki</Authors>
|
||||||
<AssemblyVersion>1.4.5</AssemblyVersion>
|
<AssemblyVersion>1.5.0</AssemblyVersion>
|
||||||
<Version>$(AssemblyVersion)</Version>
|
<Version>$(AssemblyVersion)</Version>
|
||||||
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>
|
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>
|
||||||
<RepositoryUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</RepositoryUrl>
|
<RepositoryUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</RepositoryUrl>
|
||||||
@@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
<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'">
|
||||||
|
|||||||
@@ -1,10 +1,55 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
namespace Misaki.HighPerformance.LowLevel.Utilities;
|
namespace Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum VirtualAllocationFlags
|
||||||
|
{
|
||||||
|
Reserve = 1 << 0,
|
||||||
|
Commit = 1 << 1,
|
||||||
|
}
|
||||||
|
|
||||||
public static unsafe partial class MemoryUtility
|
public static unsafe partial class MemoryUtility
|
||||||
{
|
{
|
||||||
|
private const uint _MEM_COMMIT = 0x00001000;
|
||||||
|
private const uint _MEM_RESERVE = 0x00002000;
|
||||||
|
private const uint _MEM_RELEASE = 0x00008000;
|
||||||
|
private const uint _PAGE_READWRITE = 0x04;
|
||||||
|
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
private static extern void* VirtualAlloc(void* lpAddress, nuint dwSize, uint flAllocationType, uint flProtect);
|
||||||
|
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
private static extern int VirtualFree(void* lpAddress, nuint dwSize, uint dwFreeType);
|
||||||
|
|
||||||
|
private const int _PROT_NONE = 0x0;
|
||||||
|
private const int _PROT_READ = 0x1;
|
||||||
|
private const int _PROT_WRITE = 0x2;
|
||||||
|
private const int _MAP_PRIVATE = 0x02;
|
||||||
|
|
||||||
|
// Note: MAP_ANONYMOUS varies by OS. Linux is 0x20, macOS is 0x1000.
|
||||||
|
private static int GetMapAnonymousFlag() => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 0x1000 : 0x20;
|
||||||
|
|
||||||
|
[SupportedOSPlatform("linux")]
|
||||||
|
[SupportedOSPlatform("macos")]
|
||||||
|
[DllImport("libc")]
|
||||||
|
private static extern void* mmap(void* addr, nuint length, int prot, int flags, int fd, nint offset);
|
||||||
|
|
||||||
|
[SupportedOSPlatform("linux")]
|
||||||
|
[SupportedOSPlatform("macos")]
|
||||||
|
[DllImport("libc")]
|
||||||
|
private static extern int munmap(void* addr, nuint length);
|
||||||
|
|
||||||
|
[SupportedOSPlatform("linux")]
|
||||||
|
[SupportedOSPlatform("macos")]
|
||||||
|
[DllImport("libc")]
|
||||||
|
private static extern int mprotect(void* addr, nuint len, int prot);
|
||||||
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
private struct AlignOfHelper<T>
|
private struct AlignOfHelper<T>
|
||||||
where T : struct
|
where T : struct
|
||||||
@@ -224,6 +269,79 @@ public static unsafe partial class MemoryUtility
|
|||||||
return span1.SequenceCompareTo(span2);
|
return span1.SequenceCompareTo(span2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void* Mmap(void* addr, nuint size, VirtualAllocationFlags flags)
|
||||||
|
{
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
var allocFlags = 0u;
|
||||||
|
var protect = _PAGE_READWRITE;
|
||||||
|
|
||||||
|
if (flags.HasFlag(VirtualAllocationFlags.Reserve))
|
||||||
|
{
|
||||||
|
allocFlags |= _MEM_RESERVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags.HasFlag(VirtualAllocationFlags.Commit))
|
||||||
|
{
|
||||||
|
allocFlags |= _MEM_COMMIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ptr = VirtualAlloc(addr, size, allocFlags, protect);
|
||||||
|
if (ptr == null)
|
||||||
|
{
|
||||||
|
throw new OutOfMemoryException("Failed to allocate memory using MMap.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
if (flags == VirtualAllocationFlags.Commit)
|
||||||
|
{
|
||||||
|
// POSIX commit changes protection of already-reserved memory
|
||||||
|
if (mprotect(addr, size, _PROT_READ | _PROT_WRITE) == -1)
|
||||||
|
{
|
||||||
|
throw new OutOfMemoryException("Failed to commit physical RAM via mprotect.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reserve or Reserve|Commit creates a new mapping
|
||||||
|
var prot = flags.HasFlag(VirtualAllocationFlags.Commit) ? _PROT_READ | _PROT_WRITE : _PROT_NONE;
|
||||||
|
var ptr = mmap(addr, size, prot, _MAP_PRIVATE | GetMapAnonymousFlag(), -1, 0);
|
||||||
|
|
||||||
|
if (ptr == (void*)-1)
|
||||||
|
{
|
||||||
|
throw new OutOfMemoryException("Failed to allocate memory using MMap.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new PlatformNotSupportedException("Mmap is not supported on this platform.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static bool Munmap(void* ptr, nuint size)
|
||||||
|
{
|
||||||
|
if (ptr == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
return VirtualFree(ptr, 0, _MEM_RELEASE) != 0;
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
return munmap(ptr, size) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new PlatformNotSupportedException("Munmap is not supported on this platform.");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the size in bytes of a specified unmanaged type.
|
/// Calculates the size in bytes of a specified unmanaged type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -9,10 +9,12 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<DefineConstants>$(DefineConstants);ENABLE_COLLECTION_CHECKS</DefineConstants>
|
<DefineConstants>$(DefineConstants);ENABLE_COLLECTION_CHECKS;PLATFORM_WINDOWS</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'" />
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
<DefineConstants>$(DefineConstants);PLATFORM_WINDOWS</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using BenchmarkDotNet.Running;
|
using BenchmarkDotNet.Running;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
using Misaki.HighPerformance.Mathematics.SPMD;
|
using Misaki.HighPerformance.Mathematics.SPMD;
|
||||||
@@ -10,7 +11,7 @@ using System.Text.Json;
|
|||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
BenchmarkRunner.Run<SPMDBenchmark>();
|
//BenchmarkRunner.Run<SPMDBenchmark>();
|
||||||
//var hashMap = new UnsafeHashMap<int, int>(10, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
|
//var hashMap = new UnsafeHashMap<int, int>(10, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
|
||||||
//hashMap[0] = 5;
|
//hashMap[0] = 5;
|
||||||
//hashMap[1] = 6;
|
//hashMap[1] = 6;
|
||||||
@@ -39,4 +40,15 @@ BenchmarkRunner.Run<SPMDBenchmark>();
|
|||||||
|
|
||||||
// return ref Unsafe.NullRef<int>();
|
// return ref Unsafe.NullRef<int>();
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
AllocationManager.Initialize(1024 * 1024 * 1024, 1);
|
||||||
|
|
||||||
|
// Should be undefined or throw exception because AllocationManager does not initialized.
|
||||||
|
var arr = new UnsafeArray<int>(1000, Allocator.FreeList);
|
||||||
|
for (int i = 0; i < arr.Length; i++)
|
||||||
|
{
|
||||||
|
Console.WriteLine(arr[i]);
|
||||||
|
}
|
||||||
|
arr.Dispose();
|
||||||
|
AllocationManager.Dispose();
|
||||||
|
|||||||
Reference in New Issue
Block a user