Added new TempJobAllocator

Added new AllocationHandle property in Stack.Scope.

Changed the ref AllocationHandle constructor parameter to AllocationHandle on of all UnsafeCollection types
Removed Allocator.Stack. Use Stack.Scope.AllocationHandle to allocate on stack instead.
This commit is contained in:
2025-12-06 22:16:39 +09:00
parent d6c472753d
commit f3b0f295a8
24 changed files with 301 additions and 170 deletions

View File

@@ -1,12 +1,12 @@
using Misaki.HighPerformance.Collections;
using Misaki.HighPerformance.LowLevel.Contracts;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel.Buffer;
public readonly struct MemoryHandle
public readonly struct MemoryHandle : IEquatable<MemoryHandle>
{
public readonly int id;
public readonly int generation;
@@ -20,6 +20,36 @@ public readonly struct MemoryHandle
this.id = id;
this.generation = generation;
}
public bool Equals(MemoryHandle other)
{
return id == other.id && generation == other.generation;
}
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj is MemoryHandle other && Equals(other);
}
public override int GetHashCode()
{
return id ^ generation;
}
public override string? ToString()
{
return $"MemoryHandle(Id: {id}, Generation: {generation})";
}
public static bool operator ==(MemoryHandle left, MemoryHandle right)
{
return left.Equals(right);
}
public static bool operator !=(MemoryHandle left, MemoryHandle right)
{
return !(left == right);
}
}
/// <summary>
@@ -66,7 +96,7 @@ public static unsafe class AllocationManager
private DynamicArena _arena;
private AllocationHandle _handle;
public readonly ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
public readonly AllocationHandle Handle => _handle;
public void Init(uint initialSize)
{
@@ -84,7 +114,7 @@ public static unsafe class AllocationManager
return null;
}
*pHandle = AddAllocation((IntPtr)ptr);
*pHandle = GetMagicHandle();
return ptr;
}
@@ -103,16 +133,13 @@ public static unsafe class AllocationManager
}
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
RemoveAllocation(*pHandle);
*pHandle = AddAllocation((IntPtr)newPtr);
return newPtr;
}
private static void Free(void* instance, void* ptr, MemoryHandle pHandle)
private static void Free(void* instance, void* ptr, MemoryHandle handle)
{
// The arena allocator does not free individual blocks, as it manages memory in chunks.
s_allocations.Remove(pHandle.id, pHandle.generation);
}
public void Reset()
@@ -130,7 +157,7 @@ public static unsafe class AllocationManager
{
private AllocationHandle _handle;
public readonly ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
public readonly AllocationHandle Handle => _handle;
public void Init()
{
@@ -177,11 +204,11 @@ public static unsafe class AllocationManager
private static Stack s_stack;
private AllocationHandle _handle;
public readonly ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
public readonly AllocationHandle Handle => _handle;
public void Init()
{
_handle = new(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
_handle = new(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &Free);
}
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
@@ -193,7 +220,7 @@ public static unsafe class AllocationManager
return null;
}
*pHandle = AddAllocation((IntPtr)ptr);
*pHandle = GetMagicHandle();
return ptr;
}
@@ -211,24 +238,21 @@ public static unsafe class AllocationManager
}
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
RemoveAllocation(*pHandle);
*pHandle = AddAllocation((IntPtr)newPtr);
return newPtr;
}
private static void FreeBlock(void* instance, void* ptr, MemoryHandle pHandle)
private static void Free(void* instance, void* ptr, MemoryHandle handle)
{
s_allocations.Remove(pHandle.id, pHandle.generation);
}
public static Stack.Scope CreateScope()
public static Stack.Scope CreateScope(StackAllocator* pSelf)
{
return s_stack.CreateScope();
return s_stack.CreateScope(pSelf->_handle);
}
}
private const uint _DEFAULT_MEMORY_POOL_SIZE = 512 * 1024; // 512 KB
private const uint _DEFAULT_MEMORY_POOL_SIZE = 1024 * 1024; // 1 MB
private static readonly ArenaAllocator* s_pArenaAllocator;
private static readonly HeapAllocator* s_pHeapAllocator;
@@ -247,6 +271,11 @@ public static unsafe class AllocationManager
/// </summary>
public static int LiveAllocationCount => s_allocations.Count;
/// <summary>
/// Gets a value indicating whether the debug layer is currently enabled.
/// </summary>
public static bool IsDebugLayerEnabled => s_debugLayer;
static AllocationManager()
{
s_pArenaAllocator = (ArenaAllocator*)NativeMemory.Alloc((nuint)sizeof(ArenaAllocator));
@@ -432,16 +461,14 @@ public static unsafe class AllocationManager
/// <returns>A reference to the allocation pHandle associated with the specified allocator type.</returns>
/// <exception cref="ArgumentException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref AllocationHandle GetAllocationHandle(Allocator allocator)
public static AllocationHandle GetAllocationHandle(Allocator allocator)
{
switch (allocator)
{
case Allocator.Temp:
return ref s_pArenaAllocator->Handle;
return s_pArenaAllocator->Handle;
case Allocator.Persistent:
return ref s_pHeapAllocator->Handle;
case Allocator.Stack:
return ref s_pStackAllocator->Handle;
return s_pHeapAllocator->Handle;
default:
throw new ArgumentException("Target allocator type does not support custom allocation.", nameof(allocator));
}
@@ -489,6 +516,7 @@ public static unsafe class AllocationManager
/// </summary>
/// <param name="ptr">A pointer to the memory block to be freed. The pointer must have been returned by a compatible heap allocation
/// method and must not be null.</param>
/// <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)
{
if (s_debugLayer)
@@ -503,6 +531,18 @@ public static unsafe class AllocationManager
RemoveAllocation(handle);
}
/// <summary>
/// Releases a block of unmanaged memory previously allocated by the heap allocator.
/// </summary>
/// <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)
{
if (TryGetAllocation(handle, out var ptr))
{
HeapFree((void*)ptr, handle);
}
}
/// <summary>
/// Resets the temporary memory allocator, clearing all allocated memory.
/// </summary>
@@ -519,7 +559,7 @@ public static unsafe class AllocationManager
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Stack.Scope CreateStackScope()
{
return StackAllocator.CreateScope();
return StackAllocator.CreateScope(s_pStackAllocator);
}
/// <summary>
@@ -534,6 +574,12 @@ public static unsafe class AllocationManager
return new MemoryHandle(id, generation);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryHandle GetMagicHandle()
{
return new MemoryHandle(int.MinValue, int.MinValue);
}
/// <summary>
/// Removes the memory allocation associated with the specified handle.
/// </summary>
@@ -565,6 +611,12 @@ public static unsafe class AllocationManager
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ContainsAllocation(MemoryHandle handle)
{
if (handle.id == int.MinValue && handle.generation == int.MinValue)
{
// Magic handle always valid
return true;
}
return s_allocations.Contains(handle.id, handle.generation);
}
@@ -619,7 +671,7 @@ public static unsafe class AllocationManager
throw new MemoryLeakException(CollectionsMarshal.AsSpan(snapshot));
}
}
if (LiveAllocationCount != 0)
{
throw new MemoryLeakException($"Found {LiveAllocationCount} memory lakes! Please enable debug layer for more informations.");
@@ -643,4 +695,4 @@ public static unsafe class AllocationManager
s_disposed = true;
}
}
}

View File

@@ -25,8 +25,4 @@ public enum Allocator : byte
/// Allocator for persistent allocations. Allocations are not automatically released after use.
/// </summary>
Persistent,
/// <summary>
/// Allocator for stack allocations. Must have at least one active stack scope. Allocations are automatically released when the stack scope is exited.
/// </summary>
Stack
}
}

View File

@@ -47,6 +47,8 @@ public unsafe struct DynamicArena : IDisposable
_root->arena = new Arena(initialSize);
_root->next = null;
_current = _root;
_nodeCreationLock = 0;
}
private bool TryCreateNewNode(nuint size)
@@ -165,4 +167,4 @@ public unsafe struct DynamicArena : IDisposable
_root = null;
_current = null;
}
}
}

View File

@@ -0,0 +1,73 @@
namespace Misaki.HighPerformance.LowLevel.Buffer;
/// <summary>
/// A structure that encapsulates function pointers for memory allocation operations.
/// </summary>
public readonly unsafe struct AllocationHandle
{
/// <summary>
/// Gets a pointer to the allocator instance associated with this allocation handle.
/// </summary>
public void* pAllocator
{
get;
}
/// <summary>
/// Gets a function pointer for allocating memory.
/// </summary>
public AllocFunc Alloc
{
get;
}
/// <summary>
/// Gets a function pointer for reallocating memory.
/// </summary>
public ReallocFunc Realloc
{
get;
}
/// <summary>
/// Gets a function pointer for freeing allocated memory.
/// </summary>
public FreeFunc Free
{
get;
}
/// <summary>
/// Initializes a new instance of the <see cref="AllocationHandle"/> struct with the specified allocator and memory
/// management functions.
/// </summary>
/// <param name="allocator">A pointer to the allocator instance used for memory management.</param>
/// <param name="alloc">The function used to allocate memory.</param>
/// <param name="realloc">The function used to reallocate memory.</param>
/// <param name="free">The function used to free allocated memory.</param>
public AllocationHandle(void* allocator, AllocFunc alloc, ReallocFunc realloc, FreeFunc free)
{
pAllocator = allocator;
Alloc = alloc;
Realloc = realloc;
Free = free;
}
}
/// <summary>
/// Represents an allocator interface for managing memory allocations.
/// </summary>
/// <remarks>
/// The allocator must be pined to a specific memory region.
/// Otherwise the reference of the <see cref="AllocationHandle.pAllocator"/>, may become invalid and lead to undefined behavior.
/// </remarks>
public interface IAllocator
{
/// <summary>
/// Gets a reference to the allocation handle associated with this allocator.
/// </summary>
AllocationHandle Handle
{
get;
}
}

View File

@@ -14,11 +14,15 @@ public unsafe struct Stack : IDisposable
public readonly ref struct Scope : IDisposable
{
private readonly Stack* _allocator;
private readonly AllocationHandle _handle;
private readonly nuint _originalOffset;
internal Scope(Stack* allocator)
public readonly AllocationHandle AllocationHandle => _handle;
internal Scope(Stack* allocator, AllocationHandle handle)
{
_allocator = allocator;
_handle = handle;
_originalOffset = allocator->_offset;
_allocator->_activeScopeCount++;
}
@@ -84,12 +88,15 @@ public unsafe struct Stack : IDisposable
/// <summary>
/// Creates a new scope instance associated with the current stack context.
/// </summary>
/// <remarks>
/// The instance of <see cref="Stack"/> must be pinned or allocated on the native heap to ensure that the pointer remains valid for the lifetime of the scope.
/// </remarks>
/// <returns>A <see cref="Scope"/> object that represents a scope tied to this stack.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Scope CreateScope()
public Scope CreateScope(AllocationHandle handle)
{
EnsureInitialize();
return new Scope((Stack*)Unsafe.AsPointer(ref this));
return new Scope((Stack*)Unsafe.AsPointer(ref this), handle);
}
/// <summary>
@@ -152,4 +159,4 @@ public unsafe struct Stack : IDisposable
_size = 0;
_offset = 0;
}
}
}