Files
Misaki.HighPerformance/Misaki.HighPerformance.LowLevel/Buffer/DynamicArena.cs
Misaki 9cee32aa83 feat(allocator): add per-thread caches to FreeList
Refactored FreeList allocator to use per-thread caches for improved scalability and performance, with configurable max concurrency and overflow cache. AllocationManager debug layer is now compile-time via ENABLE_DEBUG_LAYER. MemoryUtility methods no longer catch exceptions. Argument validation standardized with ThrowIfNegative. JobScheduler passes maxConcurrencyLevel to allocator. CollectionUtility's GetElementUnsafe returns mutable ref. AssemblyVersion incremented. Added comprehensive FreeList unit tests. Improved robustness and error handling in allocation classes.

BREAKING CHANGE: Debug layer APIs removed; FreeList allocator interface changed for thread cache support.
2026-03-17 20:58:31 +09:00

171 lines
4.4 KiB
C#

using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel.Buffer;
/// <summary>
/// A dynamic memory management structure that automatically grows by creating linked arenas
/// when more space is needed.
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = 128)]
public unsafe struct DynamicArena : IDisposable
{
[StructLayout(LayoutKind.Sequential)]
private struct ArenaNode
{
public Arena arena;
public ArenaNode* next;
}
[FieldOffset(0)]
private ArenaNode* _root;
[FieldOffset(8)]
private ArenaNode* _current;
[FieldOffset(16)]
private uint _initialSize;
[FieldOffset(20)]
private volatile int _nodeCreationLock;
/// <summary>
/// Initializes a new instance of DynamicArena with the specified initial size.
/// </summary>
/// <param name="initialSize">The initial size in bytes for the first arena block.</param>
public DynamicArena(uint initialSize)
{
Initialize(initialSize);
}
public void Initialize(uint initialSize)
{
if (_root != null)
{
return;
}
_initialSize = initialSize;
_root = (ArenaNode*)Malloc(SizeOf<ArenaNode>());
_root->arena = new Arena(initialSize);
_root->next = null;
_current = _root;
_nodeCreationLock = 0;
}
private bool TryCreateNewNode(nuint size)
{
while (Interlocked.CompareExchange(ref _nodeCreationLock, 1, 0) != 0)
{
Thread.SpinWait(1);
}
try
{
var current = _current;
if (current->next != null)
{
// Another thread created a node while we were waiting
_current = current->next;
return true;
}
var newNode = (ArenaNode*)Malloc(SizeOf<ArenaNode>());
try
{
newNode->arena = new Arena(size);
newNode->next = null;
// Atomically link the new node
current->next = newNode;
// Update current pointer
_current = newNode;
return true;
}
catch
{
Free(newNode);
return false;
}
}
finally
{
// Release the spinlock
Interlocked.Exchange(ref _nodeCreationLock, 0);
}
}
/// <summary>
/// Allocates a block of memory with specified size and alignment. Creates a new arena if current one is full.
/// </summary>
/// <param name="size">Size of the memory block to allocate in bytes.</param>
/// <param name="alignment">Alignment requirement for the memory block.</param>
/// <returns>Pointer to the allocated memory block.</returns>
/// <exception cref="ObjectDisposedException">Thrown if the arena has been disposed.</exception>
public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption)
{
if (_root == null)
{
return null;
}
void* result = null;
var current = _current;
while (current != null)
{
result = current->arena.Allocate(size, alignment, allocationOption);
if (result != null)
{
return result;
}
if (current->next == null && !TryCreateNewNode(Math.Max(size, _initialSize)))
{
return null;
}
current = current->next;
}
_current = current;
return result;
}
/// <summary>
/// Resets all arenas in the chain, optionally clearing their memory.
/// </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()
{
var current = _root;
while (current != null)
{
current->arena.Reset();
current = current->next;
}
_current = _root;
}
public void Dispose()
{
if (_root == null)
{
return;
}
var current = _root;
while (current != null)
{
var next = current->next;
current->arena.Dispose();
Free(current);
current = next;
}
_root = null;
_current = null;
}
}