Add TLSF allocator and refactor allocation API

- Introduced TLSF allocator with thread-safe wrapper and integrated into AllocationManager.
- Extended AllocationManagerDesc for TLSF config; made properties settable.
- Refactored AllocationHandle to encapsulate function pointers and state, replacing direct field access with methods.
- Updated all memory-related structs to use new AllocationHandle API.
- Added ReplaceIfZeros utility to MemoryUtility.
- Improved IndexOfNullByte performance.
- Minor fix in MemoryLeakException output order.
- FreeList now uses a fixed 64KB refill budget.
- Bumped version to 1.6.21; removed MHP_ENABLE_STACKTRACE from Debug.
- Updated Program.cs to test TLSF allocator and manage allocation lifecycle.
This commit is contained in:
2026-05-05 22:13:58 +09:00
parent 627c1da928
commit d3e497c7d8
14 changed files with 303 additions and 114 deletions

View File

@@ -3,6 +3,7 @@ using Misaki.HighPerformance.Collections;
#endif
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel.Buffer;
@@ -20,7 +21,7 @@ public readonly struct AllocationInfo
}
/// <summary>
/// Gets the newSize of the allocation in bytes.
/// Gets the size of the allocation in bytes.
/// </summary>
public nuint Size
{
@@ -38,40 +39,46 @@ public readonly struct AllocationInfo
#endif
}
public readonly struct AllocationManagerDesc
public struct AllocationManagerDesc
{
public required nuint ArenaCapacity
public nuint ArenaCapacity
{
get; init;
get; set;
}
public required nuint StackCapacity
public nuint StackCapacity
{
get; init;
get; set;
}
public required nuint FreeListChunkSize
public nuint FreeListChunkSize
{
get; init;
get; set;
}
public required nuint FreeListDefaultAlignment
public nuint FreeListDefaultAlignment
{
get; init;
get; set;
}
[Obsolete("FreeList concurrency level is no longer used and will be ignored. FreeList is now designed to be thread-safe without a fixed concurrency level.")]
public int FreeListConcurrencyLevel
public nuint TLSFAlignment
{
get; init;
get; set;
}
public nuint TLSFInitialChunkSize
{
get; set;
}
public static AllocationManagerDesc Default => new AllocationManagerDesc
{
ArenaCapacity = 1024 * 1024 * 1024, // 1 GB
StackCapacity = 16 * 1024 * 1024, // 16 MB per thread
StackCapacity = 32 * 1024 * 1024, // 32 MB per thread
FreeListChunkSize = 64 * 1024 * 1024,
FreeListDefaultAlignment = 8,
TLSFAlignment = 16,
TLSFInitialChunkSize = 64 * 1024, // 64 KB
};
}
@@ -89,13 +96,7 @@ public static unsafe class AllocationManager
public void Init()
{
_handle = new AllocationHandle
{
State = null,
Alloc = &Allocate,
Realloc = &Reallocate,
Free = &Free
};
_handle = new AllocationHandle(null, &Allocate, &Reallocate, &Free);
}
private static void* Allocate(void* _, nuint size, nuint alignment, AllocationOption allocationOption)
@@ -138,6 +139,63 @@ public static unsafe class AllocationManager
}
}
// TODO: Lock-free implementation
internal struct TLSFAllocator : IAllocator, IDisposable
{
private TLSF _tlsf;
private GCHandle _lock;
private AllocationHandle _handle;
public readonly AllocationHandle Handle => _handle;
public void Init(nuint alignment, nuint initialChunkSize)
{
_tlsf = new TLSF(alignment, initialChunkSize);
#pragma warning disable CS9216 // A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement.
_lock = GCHandle.Alloc(new Lock(), GCHandleType.Normal);
#pragma warning restore CS9216 // A value of type 'System.Threading.Lock' converted to a different type will use likely unintended monitor-based locking in 'lock' statement.
_handle = new AllocationHandle(Unsafe.AsPointer(in this), &Allocate, &Reallocate, &Free);
}
private static void* Allocate(void* state, nuint size, nuint alignment, AllocationOption allocationOption)
{
var allocator = (TLSFAllocator*)state;
var locker = (Lock)allocator->_lock.Target!;
lock (locker)
{
return allocator->_tlsf.Allocate(size, alignment, allocationOption);
}
}
private static void* Reallocate(void* state, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
{
var allocator = (TLSFAllocator*)state;
var locker = (Lock)allocator->_lock.Target!;
lock (locker)
{
return allocator->_tlsf.Reallocate(ptr, oldSize, newSize, alignment, allocationOption);
}
}
private static void Free(void* state, void* ptr)
{
var allocator = (TLSFAllocator*)state;
var locker = (Lock)allocator->_lock.Target!;
lock (locker)
{
allocator->_tlsf.Free(ptr);
}
}
public void Dispose()
{
_lock.Free();
}
}
private class ThreadLocalStackPool
{
public MemoryPool<VirtualStack, VirtualStack.CreationOptions> pool;
@@ -159,6 +217,7 @@ public static unsafe class AllocationManager
internal static MemoryPool<VirtualArena, VirtualArena.CreationOptions> s_arenaAllocator;
internal static MemoryPool<FreeList, FreeList.CreationOptions> s_freeListAllocator;
internal static HeapAllocator* s_pHeapAllocator;
internal static TLSFAllocator* s_pTLSFAllocator;
[ThreadStatic]
private static ThreadLocalStackPool? t_stackAllocator;
@@ -189,9 +248,8 @@ public static unsafe class AllocationManager
private static volatile bool s_initialized;
private static nuint s_threadLocalStackSize;
private static SpinLock s_stackLocker = new SpinLock(false);
public static void Initialize(AllocationManagerDesc opts)
public static void Initialize(AllocationManagerDesc desc = default)
{
if (s_initialized)
{
@@ -202,21 +260,30 @@ public static unsafe class AllocationManager
s_allocations = new ConcurrentSlotMap<AllocationInfo>(256);
#endif
var defaultDesc = AllocationManagerDesc.Default;
var spanDesc = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref desc, 1));
var spanDefault = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref defaultDesc, 1));
ReplaceIfZeros(spanDesc, spanDefault);
s_arenaAllocator = new MemoryPool<VirtualArena, VirtualArena.CreationOptions>(new VirtualArena.CreationOptions
{
reserveCapacity = opts.ArenaCapacity
reserveCapacity = desc.ArenaCapacity
});
s_freeListAllocator = new MemoryPool<FreeList, FreeList.CreationOptions>(new FreeList.CreationOptions
{
alignment = opts.FreeListDefaultAlignment,
chunkSize = opts.FreeListChunkSize
alignment = desc.FreeListDefaultAlignment,
chunkSize = desc.FreeListChunkSize
});
s_pHeapAllocator = (HeapAllocator*)Malloc((nuint)(sizeof(HeapAllocator)));
s_pHeapAllocator = (HeapAllocator*)Malloc((nuint)sizeof(HeapAllocator));
s_pHeapAllocator->Init();
s_threadLocalStackSize = opts.StackCapacity;
s_pTLSFAllocator = (TLSFAllocator*)Malloc((nuint)sizeof(TLSFAllocator));
s_pTLSFAllocator->Init(desc.TLSFAlignment, desc.TLSFInitialChunkSize);
s_threadLocalStackSize = desc.StackCapacity;
s_initialized = true;
}
@@ -423,5 +490,12 @@ public static unsafe class AllocationManager
Free(s_pHeapAllocator);
s_pHeapAllocator = null;
}
if (s_pTLSFAllocator != null)
{
s_pTLSFAllocator->Dispose();
Free(s_pTLSFAllocator);
s_pTLSFAllocator = null;
}
}
}

View File

@@ -422,8 +422,8 @@ public unsafe struct FreeList : IMemoryAllocator<FreeList, FreeList.CreationOpti
}
var blockSize = bucket->blockSize;
var blocksToCreate = Math.Max(1u, _chunkSize / blockSize);
blocksToCreate = Math.Min(blocksToCreate, 256);
const nuint REFILL_BUDGET = 64 * 1024; // 64KB per refill
var blocksToCreate = Math.Max(1u, REFILL_BUDGET / blockSize);
if (blocksToCreate == 0)
{

View File

@@ -1,3 +1,4 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace Misaki.HighPerformance.LowLevel.Buffer;
@@ -97,35 +98,39 @@ public readonly unsafe struct AllocationHandle
public static AllocationHandle Persistent => AllocationManager.s_pHeapAllocator->Handle;
/// <summary>
/// Gets a pointer to the state instance associated with this allocation handle.
/// Allocator for persistent allocations using a Two-Level Segregated Fit (TLSF) algorithm. Allocations are not automatically released after use, but can be reused to reduce fragmentation, system call and improve performance.
/// </summary>
public required void* State
public static AllocationHandle TLSF => AllocationManager.s_pTLSFAllocator->Handle;
private readonly void* _state;
private readonly AllocFunc _alloc;
private readonly ReallocFunc _realloc;
private readonly FreeFunc _free;
public AllocationHandle(void* state, AllocFunc alloc, ReallocFunc realloc, FreeFunc free)
{
get; init;
_state = state;
_alloc = alloc;
_realloc = realloc;
_free = free;
}
/// <summary>
/// Gets a function pointer for allocating memory.
/// </summary>
public required AllocFunc Alloc
public void* Alloc(nuint size, nuint alignment, AllocationOption option = AllocationOption.None)
{
get; init;
Debug.Assert(_alloc != null);
return _alloc(_state, size, alignment, option);
}
/// <summary>
/// Gets a function pointer for reallocating memory.
/// </summary>
public required ReallocFunc Realloc
public void* Realloc(void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption = AllocationOption.None)
{
get; init;
Debug.Assert(_realloc != null);
return _realloc(_state, ptr, oldSize, newSize, alignment, allocationOption);
}
/// <summary>
/// Gets a function pointer for freeing allocated memory.
/// </summary>
public required FreeFunc Free
public void Free(void* ptr)
{
get; init;
Debug.Assert(_free != null);
_free(_state, ptr);
}
}

View File

@@ -49,7 +49,7 @@ public unsafe struct MemoryBlock : IDisposable
throw new InvalidOperationException("Target allocation handle does not support allocation.");
}
_buffer = handle.Alloc(handle.State, size, alignment, allocationOption);
_buffer = handle.Alloc(size, alignment, allocationOption);
_size = size;
_alignment = alignment;
@@ -110,7 +110,7 @@ public unsafe struct MemoryBlock : IDisposable
return;
}
_buffer = _allocationHandle.Realloc(_allocationHandle.State, _buffer, _size, newSize, _alignment, option);
_buffer = _allocationHandle.Realloc(_buffer, _size, newSize, _alignment, option);
_size = newSize;
#if MHP_ENABLE_SAFETY_CHECKS
_memoryHandle.Update(_buffer, _size);
@@ -265,7 +265,7 @@ public unsafe struct MemoryBlock : IDisposable
if (_allocationHandle.Free != null)
{
_allocationHandle.Free(_allocationHandle.State, _buffer);
_allocationHandle.Free(_buffer);
}
#if MHP_ENABLE_SAFETY_CHECKS

View File

@@ -19,13 +19,7 @@ public unsafe struct MemoryPool<TAllocator, TOpts> : IDisposable
_pAllocator = (TAllocator*)Malloc((nuint)sizeof(TAllocator));
*_pAllocator = allocator;
_allocationHandle = new AllocationHandle
{
State = _pAllocator,
Alloc = &Allocate,
Realloc = &Reallocate,
Free = &Free
};
_allocationHandle = new AllocationHandle(_pAllocator, &Allocate, &Reallocate, &Free);
}
private static void* Allocate(void* pAllocator, nuint size, nuint alignment, AllocationOption allocationOption)

View File

@@ -86,8 +86,8 @@ public unsafe struct TLSF : IMemoryAllocator<TLSF, TLSF.CreationOptions>
private uint* _slBitmaps;
private BlockHeader** _blocks;
private MemoryChunk* _chunks;
private nuint _alignment;
private nuint _chunkSize;
private readonly nuint _alignment;
private readonly nuint _chunkSize;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TLSF Create(in CreationOptions opts)
@@ -141,10 +141,6 @@ public unsafe struct TLSF : IMemoryAllocator<TLSF, TLSF.CreationOptions>
{
var totalSize = size + (nuint)sizeof(MemoryChunk);
var mem = (byte*)AlignedAlloc(totalSize, _alignment);
if (mem == null)
{
throw new OutOfMemoryException("Failed to allocate MemoryChunk for TlsfAllocator.");
}
MemoryChunk* chunk = (MemoryChunk*)mem;
chunk->next = _chunks;