Enhance mathematical capabilities and job system
Added new numeric types for unsigned integers, including uint2, uint3, and uint4, along with their matrix types. Added a new `quaternion` struct with constructors and methods for creating and manipulating quaternions. Added methods for projecting and reflecting vectors, enhancing geometric operations. Added utility functions for generating orthonormal bases and changing vector signs. Added comprehensive unit tests for new mathematical functions and quaternion operations. Added a high-performance job scheduling system with job management features and worker thread management. Added new structs for job execution, allowing efficient job scheduling and execution. Added utility functions for job execution, including methods for obtaining unique job IDs. Changed access modifiers and property definitions in several files for improved clarity and maintainability. Changed property definitions and method implementations in `ImageInfo.cs`, `ImageResult.cs`, and `ImageResultFloat.cs` for better readability. Changed memory management functions in `CRuntime.cs` and improved memory allocation tracking in `MemoryStats.cs`. Changed the project file to include references to necessary projects and enable unsafe code blocks. Removed the `WorkerThreadPool.cs` file, integrating worker thread management directly into the `JobScheduler`. Removed the `float4` struct and its associated methods and properties, transitioning to a new code generation strategy. Removed the `float4.tt` template and other related files, indicating a shift in code generation approach. Removed the `Vectorize.cs` file, indicating a change in how vector operations are handled. Updated the `.gitignore` file to include IDE-specific settings. Updated various XML files to define project components and structure. Updated the `AllocationManager.cs` to improve memory allocation management and introduce new strategies. Updated the `UnsafeArray.cs`, `UnsafeHashMap.cs`, and `UnsafeList.cs` to enhance performance and safety in unsafe contexts. Updated error handling and function pointer management in `MemoryLeakException.cs` and `FunctionPointer.cs`. Updated the `AssemblyInfo.cs` file to include global using directives for better code organization.
This commit is contained in:
@@ -1,190 +1,213 @@
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Contracts;
|
||||
using Misaki.HighPerformance.LowLevel.Contracts;
|
||||
using Misaki.HighPerformance.LowLevel.Exceptions;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||
|
||||
using unsafe FreeFunc = delegate* unmanaged<void*, void*, void>;
|
||||
|
||||
public unsafe struct ArenaAllocator : IAllocator, IDisposable
|
||||
/// <summary>
|
||||
/// Holds information about a memory allocation.
|
||||
/// </summary>
|
||||
public readonly unsafe struct AllocationInfo
|
||||
{
|
||||
private DynamicArena _arena;
|
||||
private AllocationHandle _handle;
|
||||
|
||||
public readonly ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
|
||||
|
||||
public ArenaAllocator(uint initialSize)
|
||||
/// <summary>
|
||||
/// Get the size of the allocation in bytes.
|
||||
/// </summary>
|
||||
public nuint Size
|
||||
{
|
||||
_arena = new DynamicArena(initialSize);
|
||||
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
|
||||
get; init;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
|
||||
/// <summary>
|
||||
/// Get the allocator used for the allocation.
|
||||
/// </summary>
|
||||
public void* Allocator
|
||||
{
|
||||
var selfPtr = (ArenaAllocator*)instance;
|
||||
var ptr = selfPtr->_arena.Allocate(size, alignment, allocationOption);
|
||||
|
||||
return ptr;
|
||||
get; init;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment)
|
||||
/// <summary>
|
||||
/// Get the function pointer used to free the allocated memory.
|
||||
/// </summary>
|
||||
public FreeFunc FreeHandler
|
||||
{
|
||||
var selfPtr = (ArenaAllocator*)instance;
|
||||
var newPtr = selfPtr->_arena.Allocate(size, alignment, AllocationOption.None);
|
||||
MemCpy(newPtr, ptr, size);
|
||||
// NOTE: We do not free the old pointer here, as it is managed by the arena.
|
||||
return newPtr;
|
||||
get; init;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void FreeBlock(void* instance, void* ptr)
|
||||
{
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_arena.Reset();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_arena.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe struct DefaultAllocator : IAllocator
|
||||
{
|
||||
private AllocationHandle _handle;
|
||||
|
||||
public ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
|
||||
|
||||
public DefaultAllocator()
|
||||
{
|
||||
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
|
||||
{
|
||||
var ptr = AlignedAlloc(size, alignment);
|
||||
AllocationManager.TrackAllocation(ptr, size, instance, &FreeBlock);
|
||||
if (allocationOption.HasFlag(AllocationOption.Clear))
|
||||
{
|
||||
MemClear(ptr, size);
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment)
|
||||
{
|
||||
var newPtr = AlignedRealloc(ptr, size, alignment);
|
||||
AllocationManager.UpdateAllocation(ptr, newPtr, size, instance, &FreeBlock);
|
||||
return newPtr;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void FreeBlock(void* instance, void* ptr)
|
||||
{
|
||||
AlignedFree(ptr);
|
||||
AllocationManager.RemoveAllocation(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe struct EmptyAllocator : IAllocator
|
||||
{
|
||||
private AllocationHandle _handle;
|
||||
|
||||
public ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
|
||||
|
||||
public EmptyAllocator()
|
||||
{
|
||||
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment)
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void FreeBlock(void* instance, void* ptr)
|
||||
/// <summary>
|
||||
/// Get the stack trace at the time of allocation for debugging purposes.
|
||||
/// </summary>
|
||||
public StackTrace StackTrace
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides memory allocation management for native memory allocations, with support for tracking,
|
||||
/// debugging, and custom allocation strategies.
|
||||
/// </summary>
|
||||
public static unsafe class AllocationManager
|
||||
{
|
||||
public readonly struct AllocationInfo
|
||||
|
||||
private unsafe struct ArenaAllocator : IAllocator, IDisposable
|
||||
{
|
||||
public nuint Size
|
||||
private DynamicArena _arena;
|
||||
private AllocationHandle _handle;
|
||||
|
||||
public readonly ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
|
||||
|
||||
public void Init(uint initialSize)
|
||||
{
|
||||
get; init;
|
||||
_arena = new DynamicArena(initialSize);
|
||||
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
|
||||
}
|
||||
|
||||
public void* Allocator
|
||||
[UnmanagedCallersOnly]
|
||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
|
||||
{
|
||||
get; init;
|
||||
var selfPtr = (ArenaAllocator*)instance;
|
||||
var ptr = selfPtr->_arena.Allocate(size, alignment, allocationOption);
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
public FreeFunc FreeHandler
|
||||
[UnmanagedCallersOnly]
|
||||
private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment)
|
||||
{
|
||||
get; init;
|
||||
var selfPtr = (ArenaAllocator*)instance;
|
||||
var newPtr = selfPtr->_arena.Allocate(size, alignment, AllocationOption.None);
|
||||
MemCpy(newPtr, ptr, size);
|
||||
// We do not free the old pointer here, as it is managed by the arena.
|
||||
return newPtr;
|
||||
}
|
||||
|
||||
public StackTrace StackTrace
|
||||
[UnmanagedCallersOnly]
|
||||
private static void FreeBlock(void* instance, void* ptr)
|
||||
{
|
||||
get; init;
|
||||
// The arena allocator does not free individual blocks, as it manages memory in chunks.
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_arena.Reset();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_arena.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe struct HeapAllocator : IAllocator
|
||||
{
|
||||
private AllocationHandle _handle;
|
||||
|
||||
public readonly ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
|
||||
|
||||
public void Init()
|
||||
{
|
||||
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
|
||||
{
|
||||
var ptr = AlignedAlloc(size, alignment);
|
||||
|
||||
if (!allocationOption.HasFlag(AllocationOption.UnTracked))
|
||||
{
|
||||
TrackAllocation(ptr, size, instance, &FreeBlock);
|
||||
}
|
||||
|
||||
if (allocationOption.HasFlag(AllocationOption.Clear))
|
||||
{
|
||||
MemClear(ptr, size);
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment)
|
||||
{
|
||||
var newPtr = AlignedRealloc(ptr, size, alignment);
|
||||
UpdateAllocation(ptr, newPtr, size, instance, &FreeBlock);
|
||||
return newPtr;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static void FreeBlock(void* instance, void* ptr)
|
||||
{
|
||||
AlignedFree(ptr);
|
||||
UntrackAllocation(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
private const uint _DEFAULT_ARENA_SIZE = 512 * 1024;
|
||||
|
||||
private static ArenaAllocator s_arenaAllocator = new(_DEFAULT_ARENA_SIZE);
|
||||
private static DefaultAllocator s_persistentAllocator = new();
|
||||
private static EmptyAllocator s_emptyAllocator = new();
|
||||
private static readonly ArenaAllocator* s_arenaAllocator;
|
||||
private static readonly HeapAllocator* s_persistentAllocator;
|
||||
|
||||
private static bool s_debugLayer;
|
||||
private static Dictionary<nint, AllocationInfo>? s_allocated;
|
||||
private static ConcurrentDictionary<nint, AllocationInfo>? s_allocated;
|
||||
|
||||
public static ArenaAllocator TempAllocator => s_arenaAllocator;
|
||||
public static DefaultAllocator PersistentAllocator => s_persistentAllocator;
|
||||
public static EmptyAllocator EmptyAllocator => s_emptyAllocator;
|
||||
/// <summary>
|
||||
/// Gets a reference to the allocation handle for temporary allocations.
|
||||
/// </summary>
|
||||
public static ref AllocationHandle TempHandle => ref s_arenaAllocator->Handle;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to the persistent allocation handle.
|
||||
/// </summary>
|
||||
public static ref AllocationHandle PersistentHandle => ref s_persistentAllocator->Handle;
|
||||
|
||||
static AllocationManager()
|
||||
{
|
||||
s_arenaAllocator = (ArenaAllocator*)NativeMemory.Alloc((nuint)sizeof(ArenaAllocator));
|
||||
s_persistentAllocator = (HeapAllocator*)NativeMemory.Alloc((nuint)sizeof(HeapAllocator));
|
||||
|
||||
s_arenaAllocator->Init(_DEFAULT_ARENA_SIZE);
|
||||
s_persistentAllocator->Init();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables the debug layer, allowing additional diagnostic information to be collected.
|
||||
/// </summary>
|
||||
public static void EnableDebugLayer()
|
||||
{
|
||||
s_debugLayer = true;
|
||||
s_allocated ??= new Dictionary<nint, AllocationInfo>(64);
|
||||
s_allocated ??= new(-1, 64);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to the allocation handle for the specified allocator type.
|
||||
/// </summary>
|
||||
/// <param name="allocator">The allocator type for which to retrieve the allocation handle.</param>
|
||||
/// <returns>A reference to the allocation handle associated with the specified allocator type.</returns>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public static ref AllocationHandle GetAllocationHandle(Allocator allocator)
|
||||
{
|
||||
switch (allocator)
|
||||
{
|
||||
case Allocator.Temp:
|
||||
return ref s_arenaAllocator.Handle;
|
||||
return ref TempHandle;
|
||||
case Allocator.Persistent:
|
||||
return ref s_persistentAllocator.Handle;
|
||||
case Allocator.External:
|
||||
return ref s_emptyAllocator.Handle;
|
||||
return ref PersistentHandle;
|
||||
default:
|
||||
throw new ArgumentException("Invalid allocator type.", nameof(allocator));
|
||||
throw new ArgumentException("Target allocator type does not support custom allocation.", nameof(allocator));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tracks a memory allocation in the allocation manager.
|
||||
/// </summary>
|
||||
/// <param name="ptr">The pointer to the allocated memory.</param>
|
||||
/// <param name="allocationSize">The size of the allocation in bytes.</param>
|
||||
/// <param name="allocator">The allocator used for the allocation.</param>
|
||||
/// <param name="freeFunc">The function pointer used to free the allocated memory.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void TrackAllocation(void* ptr, nuint allocationSize, void* allocator, FreeFunc freeFunc)
|
||||
{
|
||||
@@ -202,6 +225,14 @@ public static unsafe class AllocationManager
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the allocation tracking information by replacing the old pointer with a new pointer.
|
||||
/// </summary>
|
||||
/// <param name="oldPtr">A pointer to the previously allocated memory. If <paramref name="oldPtr"/> is not tracked, the method does nothing.</param>
|
||||
/// <param name="newPtr">A pointer to the newly allocated memory. This pointer will replace <paramref name="oldPtr"/> in the allocation tracking.</param>
|
||||
/// <param name="allocationSize">The size, in bytes, of the new allocation.</param>
|
||||
/// <param name="allocator">A pointer to the allocator responsible for the new allocation.</param>
|
||||
/// <param name="freeFunc">A delegate or function pointer used to free the memory associated with the allocation.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void UpdateAllocation(void* oldPtr, void* newPtr, nuint allocationSize, void* allocator, FreeFunc freeFunc)
|
||||
{
|
||||
@@ -210,6 +241,7 @@ public static unsafe class AllocationManager
|
||||
return;
|
||||
}
|
||||
|
||||
// If we don't find the allocation info, that means the oldPtr was not tracked
|
||||
if (s_allocated.Remove((nint)oldPtr, out var info))
|
||||
{
|
||||
s_allocated[(nint)newPtr] = new AllocationInfo
|
||||
@@ -220,20 +252,20 @@ public static unsafe class AllocationManager
|
||||
StackTrace = info.StackTrace
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
TrackAllocation(newPtr, allocationSize, allocator, freeFunc);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveAllocation(void* ptr)
|
||||
/// <summary>
|
||||
/// Removes the specified memory allocation from the tracking system.
|
||||
/// </summary>
|
||||
/// <param name="ptr">A pointer to the memory allocation to untrack.</param>
|
||||
public static void UntrackAllocation(void* ptr)
|
||||
{
|
||||
if (s_allocated == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
s_allocated.Remove((nint)ptr);
|
||||
s_allocated.Remove((nint)ptr, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -241,8 +273,6 @@ public static unsafe class AllocationManager
|
||||
/// </summary>
|
||||
public static void Dispose()
|
||||
{
|
||||
s_arenaAllocator.Dispose();
|
||||
|
||||
if (s_allocated != null)
|
||||
{
|
||||
nuint unfreeBytes = 0u;
|
||||
@@ -259,5 +289,16 @@ public static unsafe class AllocationManager
|
||||
|
||||
s_allocated.Clear();
|
||||
}
|
||||
|
||||
if (s_arenaAllocator != null)
|
||||
{
|
||||
s_arenaAllocator->Dispose();
|
||||
NativeMemory.Free(s_arenaAllocator);
|
||||
}
|
||||
|
||||
if (s_persistentAllocator != null)
|
||||
{
|
||||
NativeMemory.Free(s_persistentAllocator);
|
||||
}
|
||||
}
|
||||
}
|
||||
33
Misaki.HighPerformance.LowLevel/Buffer/AllocationOption.cs
Normal file
33
Misaki.HighPerformance.LowLevel/Buffer/AllocationOption.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||
|
||||
[Flags]
|
||||
public enum AllocationOption : byte
|
||||
{
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// Allocator for initialized memory.
|
||||
/// </summary>
|
||||
Clear = 1 << 0,
|
||||
/// <summary>
|
||||
/// Allocator for untracked memory.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use this option carefully, as the allocation manager will not track the memory.
|
||||
/// No warning will be given if the memory is not freed.
|
||||
/// </remarks>
|
||||
UnTracked = 1 << 1,
|
||||
}
|
||||
|
||||
public enum Allocator : byte
|
||||
{
|
||||
// Make the first allocator as invalid because we don't want to user create a default collection without passing any parameters
|
||||
Invalid,
|
||||
/// <summary>
|
||||
/// Allocator for temporary allocations. Allocations are cleared after use.
|
||||
/// </summary>
|
||||
Temp,
|
||||
/// <summary>
|
||||
/// Allocator for persistent allocations. Allocations are not cleared after use.
|
||||
/// </summary>
|
||||
Persistent
|
||||
}
|
||||
@@ -1,18 +1,20 @@
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||
|
||||
/// <summary>
|
||||
/// A memory management structure that allocates and resets memory blocks with specified alignment.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit, Size = 64)] // Cache line aligned to prevent false sharing
|
||||
public unsafe struct Arena : IDisposable
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
private byte* _buffer;
|
||||
[FieldOffset(8)]
|
||||
private nuint _size;
|
||||
[FieldOffset(16)]
|
||||
private nuint _offset;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public Arena(nuint size)
|
||||
{
|
||||
Initialize(size);
|
||||
@@ -42,20 +44,36 @@ public unsafe struct Arena : IDisposable
|
||||
/// <exception cref="ObjectDisposedException">Thrown if the arena has been disposed.</exception>
|
||||
public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption)
|
||||
{
|
||||
if (_disposed)
|
||||
if (_buffer == null)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(DynamicArena));
|
||||
}
|
||||
|
||||
var offset = _offset + alignment - 1 & ~(alignment - 1);
|
||||
if (offset + size > _size)
|
||||
//var offset = _offset + alignment - 1 & ~(alignment - 1);
|
||||
//if (offset + size > _size)
|
||||
//{
|
||||
// return null;
|
||||
//}
|
||||
|
||||
//_offset = offset + size;
|
||||
//var ptr = _buffer + offset;
|
||||
|
||||
nuint currentOffset, newOffset, alignedOffset;
|
||||
|
||||
do
|
||||
{
|
||||
return null;
|
||||
}
|
||||
currentOffset = _offset;
|
||||
alignedOffset = (currentOffset + alignment - 1) & ~(alignment - 1);
|
||||
newOffset = alignedOffset + size;
|
||||
|
||||
_offset = offset + size;
|
||||
var ptr = _buffer + offset;
|
||||
if (newOffset > _size)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
} while (Interlocked.CompareExchange(ref _offset, newOffset, currentOffset) != currentOffset);
|
||||
|
||||
var ptr = _buffer + alignedOffset;
|
||||
if (allocationOption.HasFlag(AllocationOption.Clear))
|
||||
{
|
||||
MemClear(ptr, size);
|
||||
@@ -71,7 +89,7 @@ public unsafe struct Arena : IDisposable
|
||||
/// <exception cref="ObjectDisposedException">Thrown if the arena has been disposed.</exception>
|
||||
public void Reset()
|
||||
{
|
||||
if (_disposed)
|
||||
if (_buffer == null)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(DynamicArena));
|
||||
}
|
||||
@@ -81,12 +99,15 @@ public unsafe struct Arena : IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_buffer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Free(_buffer);
|
||||
|
||||
_buffer = null;
|
||||
_size = 0;
|
||||
_offset = 0;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||
|
||||
@@ -6,29 +6,33 @@ namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||
/// 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)
|
||||
{
|
||||
_initialSize = initialSize;
|
||||
_root = (ArenaNode*)Malloc(SizeOf<ArenaNode>());
|
||||
_root->arena = new Arena(initialSize);
|
||||
_root->next = null;
|
||||
_current = _root;
|
||||
Initialize(initialSize);
|
||||
}
|
||||
|
||||
public void Initialize(uint initialSize)
|
||||
@@ -45,23 +49,63 @@ public unsafe struct DynamicArena : IDisposable
|
||||
_current = _root;
|
||||
}
|
||||
|
||||
private bool CreateNewNode(nuint size)
|
||||
private bool TryCreateNewNode(nuint size)
|
||||
{
|
||||
var newNode = (ArenaNode*)Malloc(SizeOf<ArenaNode>());
|
||||
while (Interlocked.CompareExchange(ref _nodeCreationLock, 1, 0) != 0)
|
||||
{
|
||||
Thread.SpinWait(1);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
newNode->arena = new Arena(size);
|
||||
newNode->next = null;
|
||||
var current = _current;
|
||||
if (current->next != null)
|
||||
{
|
||||
// Another thread created a node while we were waiting
|
||||
_current = current->next;
|
||||
return true;
|
||||
}
|
||||
|
||||
_current->next = newNode;
|
||||
_current = newNode;
|
||||
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;
|
||||
}
|
||||
}
|
||||
catch
|
||||
finally
|
||||
{
|
||||
Free(newNode);
|
||||
return false;
|
||||
// Release the spinlock
|
||||
Interlocked.Exchange(ref _nodeCreationLock, 0);
|
||||
}
|
||||
|
||||
//var newNode = (ArenaNode*)Malloc(SizeOf<ArenaNode>());
|
||||
//try
|
||||
//{
|
||||
// newNode->arena = new Arena(size);
|
||||
// newNode->next = null;
|
||||
|
||||
// _current->next = newNode;
|
||||
// _current = newNode;
|
||||
// return true;
|
||||
//}
|
||||
//catch
|
||||
//{
|
||||
// Free(newNode);
|
||||
// return false;
|
||||
//}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -89,7 +133,7 @@ public unsafe struct DynamicArena : IDisposable
|
||||
return result;
|
||||
}
|
||||
|
||||
if (current->next == null && !CreateNewNode(Math.Max(size, _initialSize)))
|
||||
if (current->next == null && !TryCreateNewNode(Math.Max(size, _initialSize)))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ public unsafe struct FixedStackString32
|
||||
fixed (byte* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ public unsafe struct FixedStackString64
|
||||
fixed (byte* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,7 +316,7 @@ public unsafe struct FixedStackString128
|
||||
fixed (byte* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,7 +431,7 @@ public unsafe struct FixedStackString256
|
||||
fixed (byte* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,7 +546,7 @@ public unsafe struct FixedStackString512
|
||||
fixed (byte* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -661,7 +661,7 @@ public unsafe struct FixedStackString1024
|
||||
fixed (byte* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -776,7 +776,7 @@ public unsafe struct FixedStackString2048
|
||||
fixed (byte* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -891,7 +891,7 @@ public unsafe struct FixedStackString4096
|
||||
fixed (byte* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ public unsafe struct FixedStackString<#= i #>
|
||||
fixed (byte* inputPtr = input)
|
||||
fixed (byte* bufferPtr = _buffer)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
Unsafe.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ public unsafe struct FixedString32 : IDisposable
|
||||
|
||||
fixed (byte* inputPtr = input)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
|
||||
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ public unsafe struct FixedString32 : IDisposable
|
||||
{
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
@@ -188,7 +188,7 @@ public unsafe struct FixedString64 : IDisposable
|
||||
|
||||
fixed (byte* inputPtr = input)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
|
||||
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ public unsafe struct FixedString64 : IDisposable
|
||||
{
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
@@ -299,7 +299,7 @@ public unsafe struct FixedString128 : IDisposable
|
||||
|
||||
fixed (byte* inputPtr = input)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
|
||||
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,7 +319,7 @@ public unsafe struct FixedString128 : IDisposable
|
||||
{
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
@@ -410,7 +410,7 @@ public unsafe struct FixedString256 : IDisposable
|
||||
|
||||
fixed (byte* inputPtr = input)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
|
||||
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,7 +430,7 @@ public unsafe struct FixedString256 : IDisposable
|
||||
{
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
@@ -521,7 +521,7 @@ public unsafe struct FixedString512 : IDisposable
|
||||
|
||||
fixed (byte* inputPtr = input)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
|
||||
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,7 +541,7 @@ public unsafe struct FixedString512 : IDisposable
|
||||
{
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
@@ -632,7 +632,7 @@ public unsafe struct FixedString1024 : IDisposable
|
||||
|
||||
fixed (byte* inputPtr = input)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
|
||||
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -652,7 +652,7 @@ public unsafe struct FixedString1024 : IDisposable
|
||||
{
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
@@ -743,7 +743,7 @@ public unsafe struct FixedString2048 : IDisposable
|
||||
|
||||
fixed (byte* inputPtr = input)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
|
||||
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -763,7 +763,7 @@ public unsafe struct FixedString2048 : IDisposable
|
||||
{
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
@@ -854,7 +854,7 @@ public unsafe struct FixedString4096 : IDisposable
|
||||
|
||||
fixed (byte* inputPtr = input)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
|
||||
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -874,7 +874,7 @@ public unsafe struct FixedString4096 : IDisposable
|
||||
{
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
|
||||
@@ -84,7 +84,7 @@ public unsafe struct FixedString<#= i #> : IDisposable
|
||||
|
||||
fixed (byte* inputPtr = input)
|
||||
{
|
||||
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
|
||||
Unsafe.CopyBlockUnaligned(_buffer, inputPtr, _length);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
479
Misaki.HighPerformance.LowLevel/Buffer/FreeList.cs
Normal file
479
Misaki.HighPerformance.LowLevel/Buffer/FreeList.cs
Normal file
@@ -0,0 +1,479 @@
|
||||
using Misaki.HighPerformance.LowLevel.Helpers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||
|
||||
/// <summary>
|
||||
/// A lock-free, thread-safe variable-size allocator that manages memory blocks of different sizes.
|
||||
/// Optimized for high-performance scenarios with frequent allocations and deallocations.
|
||||
///
|
||||
/// Example usage:
|
||||
/// <code>
|
||||
/// // Create a free list with multiple size buckets
|
||||
/// var freeList = new FreeList();
|
||||
///
|
||||
/// // Allocate a 70-byte block
|
||||
/// var block = freeList.Allocate(70);
|
||||
/// if (block.IsValid)
|
||||
/// {
|
||||
/// // Use the memory block...
|
||||
///
|
||||
/// // Free the block when done
|
||||
/// freeList.Free(block);
|
||||
/// }
|
||||
///
|
||||
/// // Dispose when finished
|
||||
/// freeList.Dispose();
|
||||
/// </code>
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit, Size = 256)] // Cache line aligned to prevent false sharing
|
||||
public unsafe struct FreeList : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Node structure for the lock-free free list with size information.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct FreeNode
|
||||
{
|
||||
public FreeNode* next;
|
||||
public nuint size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Memory chunk that contains variable-size blocks.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct MemoryChunk
|
||||
{
|
||||
public MemoryChunk* next;
|
||||
public byte* memory;
|
||||
public nuint size;
|
||||
public nuint used; // Amount of memory used in this chunk
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Size bucket for different allocation sizes.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct SizeBucket
|
||||
{
|
||||
public nint freeHead; // Free list head for this size
|
||||
public nuint blockSize; // Fixed size for this bucket
|
||||
public long freeCount; // Number of free blocks
|
||||
}
|
||||
|
||||
private const int _MAX_BUCKETS = 16; // Number of size buckets
|
||||
private const nuint _MIN_BLOCK_SIZE = 16; // Minimum block size
|
||||
private const nuint _DEFAULT_CHUNK_SIZE = 64 * 1024; // 64KB chunks
|
||||
|
||||
[FieldOffset(0)]
|
||||
private fixed byte _buckets[_MAX_BUCKETS * 32]; // SizeBucket array (32 bytes per bucket)
|
||||
|
||||
[FieldOffset(512)]
|
||||
private DynamicArena _chunkArena; // 128
|
||||
|
||||
[FieldOffset(640)]
|
||||
private MemoryChunk* _chunks; // 8
|
||||
|
||||
[FieldOffset(648)]
|
||||
private nuint _chunkSize; // 8
|
||||
|
||||
[FieldOffset(656)]
|
||||
private nuint _alignment; // 8
|
||||
|
||||
[FieldOffset(664)]
|
||||
private long _totalAllocatedBytes; // 8
|
||||
|
||||
[FieldOffset(672)]
|
||||
private long _totalFreeBytes; // 8
|
||||
|
||||
[FieldOffset(676)]
|
||||
private volatile int _disposed; // 4
|
||||
|
||||
[FieldOffset(680)]
|
||||
private volatile int _chunkCreationLock; // 4
|
||||
|
||||
/// <summary>
|
||||
/// Gets the alignment requirement for allocations.
|
||||
/// </summary>
|
||||
public readonly nuint Alignment => _alignment;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of allocated bytes.
|
||||
/// </summary>
|
||||
public readonly long TotalAllocatedBytes => Interlocked.Read(ref Unsafe.AsRef(in _totalAllocatedBytes));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of free bytes available.
|
||||
/// </summary>
|
||||
public readonly long TotalFreeBytes => Interlocked.Read(ref Unsafe.AsRef(in _totalFreeBytes));
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the allocator has been disposed.
|
||||
/// </summary>
|
||||
public readonly bool IsDisposed => _disposed != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the chunk size used by this allocator.
|
||||
/// </summary>
|
||||
public readonly nuint ChunkSize => _chunkSize;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new variable-size FreeList allocator with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="alignment">Alignment requirement for blocks (must be power of 2).</param>
|
||||
/// <param name="chunkSize">Size of memory chunks to allocate (default: 64KB).</param>
|
||||
public FreeList(nuint alignment = 8, nuint chunkSize = _DEFAULT_CHUNK_SIZE)
|
||||
{
|
||||
if (alignment == 0 || (alignment & (alignment - 1)) != 0)
|
||||
throw new ArgumentException("Alignment must be a power of 2", nameof(alignment));
|
||||
|
||||
if (chunkSize < 1024)
|
||||
throw new ArgumentException("Chunk size must be at least 1KB", nameof(chunkSize));
|
||||
|
||||
_alignment = alignment;
|
||||
_chunkSize = chunkSize;
|
||||
_chunks = null;
|
||||
_totalAllocatedBytes = 0;
|
||||
_totalFreeBytes = 0;
|
||||
_disposed = 0;
|
||||
_chunkCreationLock = 0;
|
||||
|
||||
_chunkArena = new DynamicArena(1024);
|
||||
InitializeBuckets();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the size buckets with exponential sizes.
|
||||
/// </summary>
|
||||
private readonly void InitializeBuckets()
|
||||
{
|
||||
var buckets = GetBuckets();
|
||||
var size = _MIN_BLOCK_SIZE;
|
||||
|
||||
for (var i = 0; i < _MAX_BUCKETS; i++)
|
||||
{
|
||||
buckets[i].blockSize = size;
|
||||
buckets[i].freeHead = 0;
|
||||
buckets[i].freeCount = 0;
|
||||
size *= 2; // Exponential size increase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a pointer to the size buckets array.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private readonly SizeBucket* GetBuckets()
|
||||
{
|
||||
fixed (byte* ptr = _buckets)
|
||||
{
|
||||
return (SizeBucket*)ptr;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the appropriate bucket for the given size.
|
||||
/// </summary>
|
||||
/// <param name="size">Size to find bucket for.</param>
|
||||
/// <returns>Bucket index, or -1 if too large for buckets.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private readonly int FindBucket(nuint size)
|
||||
{
|
||||
var buckets = GetBuckets();
|
||||
|
||||
for (var i = 0; i < _MAX_BUCKETS; i++)
|
||||
{
|
||||
if (size <= buckets[i].blockSize)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1; // Size too large for buckets
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocates a memory block of the specified size. Thread-safe using lock-free algorithms.
|
||||
/// </summary>
|
||||
/// <param name="size">Size of memory to allocate in bytes.</param>
|
||||
/// <param name="alignment">Alignment requirement (0 = use default).</param>
|
||||
/// <param name="allocationOption">Options for allocation (e.g., clear memory).</param>
|
||||
/// <returns>MemoryBlock containing allocated memory, or Invalid if allocation fails.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public MemoryBlock Allocate(nuint size, nuint alignment = 0, AllocationOption allocationOption = AllocationOption.None)
|
||||
{
|
||||
if (_disposed != 0 || size == 0)
|
||||
return MemoryBlock.Invalid;
|
||||
|
||||
if (alignment == 0)
|
||||
alignment = _alignment;
|
||||
|
||||
// Align size to alignment boundary
|
||||
var alignedSize = (size + alignment - 1) & ~(alignment - 1);
|
||||
alignedSize = Math.Max(alignedSize, _MIN_BLOCK_SIZE);
|
||||
|
||||
var bucketIndex = FindBucket(alignedSize);
|
||||
void* ptr = null;
|
||||
|
||||
if (bucketIndex >= 0)
|
||||
{
|
||||
// Try to allocate from bucket
|
||||
ptr = TryPopFromBucket(bucketIndex);
|
||||
|
||||
if (ptr == null)
|
||||
{
|
||||
// Create new blocks for this bucket
|
||||
if (TryCreateBlocksForBucket(bucketIndex))
|
||||
{
|
||||
ptr = TryPopFromBucket(bucketIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ptr == null)
|
||||
{
|
||||
// Fallback to direct allocation from chunk
|
||||
ptr = AllocateFromChunk(alignedSize, alignment);
|
||||
}
|
||||
|
||||
if (ptr != null)
|
||||
{
|
||||
Interlocked.Add(ref _totalAllocatedBytes, (long)alignedSize);
|
||||
|
||||
if (allocationOption.HasFlag(AllocationOption.Clear))
|
||||
{
|
||||
MemClear(ptr, alignedSize);
|
||||
}
|
||||
|
||||
return new MemoryBlock(ptr, alignedSize, alignment);
|
||||
}
|
||||
|
||||
return MemoryBlock.Invalid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees a previously allocated memory block. Thread-safe using lock-free algorithms.
|
||||
/// </summary>
|
||||
/// <param name="block">MemoryBlock to free.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Free(MemoryBlock block)
|
||||
{
|
||||
if (!block.IsValid || _disposed != 0)
|
||||
return;
|
||||
|
||||
if (!IsValidBlock(block.Ptr))
|
||||
return; // Invalid pointer, ignore
|
||||
|
||||
var bucketIndex = FindBucket(block.Size);
|
||||
if (bucketIndex >= 0)
|
||||
{
|
||||
PushToBucket(bucketIndex, block.Ptr, block.Size);
|
||||
}
|
||||
|
||||
Interlocked.Add(ref _totalAllocatedBytes, -(long)block.Size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to pop a free block from the specified bucket.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private readonly void* TryPopFromBucket(int bucketIndex)
|
||||
{
|
||||
var buckets = GetBuckets();
|
||||
var bucket = &buckets[bucketIndex];
|
||||
|
||||
nint head, newHead;
|
||||
FreeNode* headPtr;
|
||||
|
||||
do
|
||||
{
|
||||
head = bucket->freeHead;
|
||||
if (head == 0)
|
||||
return null;
|
||||
|
||||
headPtr = (FreeNode*)head;
|
||||
newHead = (nint)headPtr->next;
|
||||
|
||||
} while (Interlocked.CompareExchange(ref bucket->freeHead, newHead, head) != head);
|
||||
|
||||
Interlocked.Decrement(ref bucket->freeCount);
|
||||
return (void*)head;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes a block to the specified bucket's free list.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private readonly void PushToBucket(int bucketIndex, void* ptr, nuint size)
|
||||
{
|
||||
var buckets = GetBuckets();
|
||||
var bucket = &buckets[bucketIndex];
|
||||
var node = (FreeNode*)ptr;
|
||||
|
||||
node->size = size;
|
||||
|
||||
nint head;
|
||||
do
|
||||
{
|
||||
head = bucket->freeHead;
|
||||
node->next = (FreeNode*)head;
|
||||
|
||||
} while (Interlocked.CompareExchange(ref bucket->freeHead, (nint)node, head) != head);
|
||||
|
||||
Interlocked.Increment(ref bucket->freeCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates new blocks for the specified bucket.
|
||||
/// </summary>
|
||||
private bool TryCreateBlocksForBucket(int bucketIndex)
|
||||
{
|
||||
while (Interlocked.CompareExchange(ref _chunkCreationLock, 1, 0) != 0)
|
||||
{
|
||||
Thread.SpinWait(1);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var buckets = GetBuckets();
|
||||
var blockSize = buckets[bucketIndex].blockSize;
|
||||
var blocksToCreate = Math.Min(_chunkSize / blockSize, 256); // Limit number of blocks
|
||||
|
||||
if (blocksToCreate == 0)
|
||||
return false;
|
||||
|
||||
var totalSize = blocksToCreate * blockSize;
|
||||
var memory = (byte*)AlignedAlloc(totalSize, _alignment);
|
||||
if (memory == null)
|
||||
return false;
|
||||
|
||||
var chunk = (MemoryChunk*)_chunkArena.Allocate(SizeOf<MemoryChunk>(), AlignOf<MemoryChunk>(), AllocationOption.None);
|
||||
if (chunk == null)
|
||||
{
|
||||
AlignedFree(memory);
|
||||
return false;
|
||||
}
|
||||
|
||||
chunk->memory = memory;
|
||||
chunk->size = totalSize;
|
||||
chunk->used = totalSize;
|
||||
chunk->next = _chunks;
|
||||
_chunks = chunk;
|
||||
|
||||
// Add all blocks to the bucket's free list
|
||||
for (nuint i = 0; i < blocksToCreate; i++)
|
||||
{
|
||||
var blockPtr = memory + (i * blockSize);
|
||||
PushToBucket(bucketIndex, blockPtr, blockSize);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Exchange(ref _chunkCreationLock, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocates memory directly from a chunk (for large allocations).
|
||||
/// </summary>
|
||||
private void* AllocateFromChunk(nuint size, nuint alignment)
|
||||
{
|
||||
while (Interlocked.CompareExchange(ref _chunkCreationLock, 1, 0) != 0)
|
||||
{
|
||||
Thread.SpinWait(1);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Try to find space in existing chunks first
|
||||
var chunk = _chunks;
|
||||
while (chunk != null)
|
||||
{
|
||||
var available = chunk->size - chunk->used;
|
||||
var alignedOffset = (chunk->used + alignment - 1) & ~(alignment - 1);
|
||||
var totalNeeded = alignedOffset - chunk->used + size;
|
||||
|
||||
if (totalNeeded <= available)
|
||||
{
|
||||
var ptr = chunk->memory + alignedOffset;
|
||||
chunk->used += totalNeeded;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
chunk = chunk->next;
|
||||
}
|
||||
|
||||
// Create new chunk
|
||||
var newChunkSize = Math.Max(_chunkSize, size + alignment);
|
||||
var newMemory = (byte*)AlignedAlloc(newChunkSize, alignment);
|
||||
if (newMemory == null)
|
||||
return null;
|
||||
|
||||
var newChunk = (MemoryChunk*)_chunkArena.Allocate(SizeOf<MemoryChunk>(), AlignOf<MemoryChunk>(), AllocationOption.None);
|
||||
if (newChunk == null)
|
||||
{
|
||||
AlignedFree(newMemory);
|
||||
return null;
|
||||
}
|
||||
|
||||
newChunk->memory = newMemory;
|
||||
newChunk->size = newChunkSize;
|
||||
newChunk->used = size;
|
||||
newChunk->next = _chunks;
|
||||
_chunks = newChunk;
|
||||
|
||||
return newMemory;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Exchange(ref _chunkCreationLock, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that a pointer belongs to one of our memory chunks.
|
||||
/// </summary>
|
||||
private readonly bool IsValidBlock(void* ptr)
|
||||
{
|
||||
var chunk = _chunks;
|
||||
while (chunk != null)
|
||||
{
|
||||
var chunkStart = (nuint)chunk->memory;
|
||||
var chunkEnd = chunkStart + chunk->size;
|
||||
var ptrValue = (nuint)ptr;
|
||||
|
||||
if (ptrValue >= chunkStart && ptrValue < chunkEnd)
|
||||
return true;
|
||||
|
||||
chunk = chunk->next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the free list and frees all allocated memory.
|
||||
/// Note: This method is NOT thread-safe by design as requested.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _disposed, 1, 0) == 0)
|
||||
{
|
||||
// Free all memory chunks
|
||||
var chunk = _chunks;
|
||||
while (chunk != null)
|
||||
{
|
||||
var next = chunk->next;
|
||||
AlignedFree(chunk->memory);
|
||||
MemoryUtilities.Free(chunk);
|
||||
chunk = next;
|
||||
}
|
||||
|
||||
_chunks = null;
|
||||
_totalAllocatedBytes = 0;
|
||||
_totalFreeBytes = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Misaki.HighPerformance.LowLevel/Buffer/MemoryBlock.cs
Normal file
68
Misaki.HighPerformance.LowLevel/Buffer/MemoryBlock.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an allocated memory block with metadata.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public unsafe readonly struct MemoryBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// Pointer to the actual allocated memory.
|
||||
/// </summary>
|
||||
public void* Ptr
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Size of the allocated memory in bytes.
|
||||
/// </summary>
|
||||
public nuint Size
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Alignment of the allocated memory.
|
||||
/// </summary>
|
||||
public nuint Alignment
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether this memory block is valid.
|
||||
/// </summary>
|
||||
public readonly bool IsValid => Ptr != null && Size > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new MemoryBlock with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="ptr">Pointer to the allocated memory.</param>
|
||||
/// <param name="size">Size of the allocated memory.</param>
|
||||
/// <param name="alignment">Alignment of the allocated memory.</param>
|
||||
public MemoryBlock(void* ptr, nuint size, nuint alignment)
|
||||
{
|
||||
Ptr = ptr;
|
||||
Size = size;
|
||||
Alignment = alignment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an invalid MemoryBlock.
|
||||
/// </summary>
|
||||
public static MemoryBlock Invalid => new(null, 0, 0);
|
||||
|
||||
public Span<T> AsSpan<T>()
|
||||
where T : unmanaged
|
||||
{
|
||||
if (!IsValid)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot create span from invalid MemoryBlock.");
|
||||
}
|
||||
|
||||
return new Span<T>(Ptr, (int)(Size / SizeOf<T>()));
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
|
||||
namespace Misaki.HighPerformance.LowLevel.Buffer;
|
||||
|
||||
// TODO: Implement a pool for UnsafeArray<T>.
|
||||
public unsafe static class UnsafeArrayPool
|
||||
{
|
||||
public static UnsafeArray<T> Rent<T>(int minimalSize)
|
||||
where T : unmanaged
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static void Return<T>(UnsafeArray<T> array)
|
||||
where T : unmanaged
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user