From f73c09add55e0673ccbb5ab63da2b3a18621dca3 Mon Sep 17 00:00:00 2001 From: Jim <0xJ1M@users.noreply.github.com> Date: Mon, 23 Mar 2026 22:26:01 +0000 Subject: [PATCH] WIP: Removed internal header tracking for WorkspaceHeap. Modified Handles to track the alignment that the underlying memory was aligned to. Re-worked WorkspaceHeap to use lists rather than Stack --- UnmangedMMU/Handles/Internal/IOwnedHandle.cs | 5 + UnmangedMMU/Handles/MemoryHandleBase.cs | 17 +- UnmangedMMU/Handles/PersistentMemoryHandle.cs | 2 +- UnmangedMMU/Handles/SegmentedMemoryHandle.cs | 9 +- UnmangedMMU/SegmentedPool.cs | 8 +- UnmangedMMU/WorkspaceHeap.cs | 312 +++++++++++------- 6 files changed, 215 insertions(+), 138 deletions(-) diff --git a/UnmangedMMU/Handles/Internal/IOwnedHandle.cs b/UnmangedMMU/Handles/Internal/IOwnedHandle.cs index eadde46..bd2fcc2 100644 --- a/UnmangedMMU/Handles/Internal/IOwnedHandle.cs +++ b/UnmangedMMU/Handles/Internal/IOwnedHandle.cs @@ -11,5 +11,10 @@ namespace UnmanagedMMU.Handles.Internal /// Returns the that created owns this /// IUnmanagedMemoryOwner GetOwner(); + + /// + /// Returns the memory alignment for the + /// + nuint Alignment { get; } } } diff --git a/UnmangedMMU/Handles/MemoryHandleBase.cs b/UnmangedMMU/Handles/MemoryHandleBase.cs index d3d6143..8d0d0b5 100644 --- a/UnmangedMMU/Handles/MemoryHandleBase.cs +++ b/UnmangedMMU/Handles/MemoryHandleBase.cs @@ -41,18 +41,25 @@ namespace UnmanagedMMU.Handles /// private bool _disposed; + /// + /// The alignment of the memory pointed to by + /// + private readonly nuint _alignment; + /// /// Initializes a new instnace /// /// Pointer to the allocated unmanaged memory /// The size of the unallocated memory block in bytes + /// The alignment that the unmanged memory pointed to by /// The that owns the handle being created - protected MemoryHandleBase(void* ptr, nuint byteLength, IUnmanagedMemoryOwner owner) + protected MemoryHandleBase(void* ptr, nuint byteLength, nuint alignment, IUnmanagedMemoryOwner owner) { // Defensive check Debug.Assert(ptr != null, message: "BUG CHECK: E_INVALID_MEMORY_HANDLE"); _ptr = ptr; _bytelen = byteLength; + _alignment = alignment; _owner = owner; } @@ -64,6 +71,14 @@ namespace UnmanagedMMU.Handles get { return _ptr; } } + /// + /// Returns the memory alignment for the + /// + public virtual nuint Alignment + { + get { return _alignment; } + } + /// /// Gets the typed pointer to the unmanged memory block /// diff --git a/UnmangedMMU/Handles/PersistentMemoryHandle.cs b/UnmangedMMU/Handles/PersistentMemoryHandle.cs index 79e1c72..6bb780f 100644 --- a/UnmangedMMU/Handles/PersistentMemoryHandle.cs +++ b/UnmangedMMU/Handles/PersistentMemoryHandle.cs @@ -5,7 +5,7 @@ namespace UnmanagedMMU.Handles internal sealed unsafe class PersistentMemoryHandle : MemoryHandleBase where T : unmanaged { - public PersistentMemoryHandle(void* ptr, nuint byteLength, IUnmanagedMemoryOwner owner) : base(ptr, byteLength, owner) + public PersistentMemoryHandle(void* ptr, nuint byteLength, nuint alignment, IUnmanagedMemoryOwner owner) : base(ptr, byteLength, alignment, owner) { } diff --git a/UnmangedMMU/Handles/SegmentedMemoryHandle.cs b/UnmangedMMU/Handles/SegmentedMemoryHandle.cs index 7e4168c..ec8e21f 100644 --- a/UnmangedMMU/Handles/SegmentedMemoryHandle.cs +++ b/UnmangedMMU/Handles/SegmentedMemoryHandle.cs @@ -1,15 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using UnmanagedMMU.Allocators; +using UnmanagedMMU.Allocators; namespace UnmanagedMMU.Handles { internal sealed unsafe class SegmentedMemoryHandle : MemoryHandleBase where T : unmanaged { - public SegmentedMemoryHandle(void* ptr, nuint byteLength, IUnmanagedMemoryOwner owner) : base(ptr, byteLength, owner) + public SegmentedMemoryHandle(void* ptr, nuint byteLength, nuint alignment, IUnmanagedMemoryOwner owner) : base(ptr, byteLength, alignment, owner) { } diff --git a/UnmangedMMU/SegmentedPool.cs b/UnmangedMMU/SegmentedPool.cs index 118fdb9..db9abca 100644 --- a/UnmangedMMU/SegmentedPool.cs +++ b/UnmangedMMU/SegmentedPool.cs @@ -291,7 +291,7 @@ /// /// Thrown if is zero, or if is less than 1. /// - public SegmentedPool(nuint segmentSize = _defaultSegmentSize, SegmentAlignment segmentAlignment = SegmentAlignment.Aligned32, int initialSegments = 4, bool zeroMemory = false) + public SegmentedPool(nuint segmentSize = _defaultSegmentSize, SegmentAlignment segmentAlignment = SegmentAlignment.Aligned16, int initialSegments = 4, bool zeroMemory = false) : this(segmentSize, segmentAlignment, initialSegments, zeroMemory, new DefaultUnmanagedAllocator()) { } @@ -620,7 +620,9 @@ { T* ptr = Alloc(count); nuint byteLength = (nuint)count * (nuint)sizeof(T); - return new SegmentedMemoryHandle(ptr, byteLength, this); + + nuint alignment = _segmentAlignment > (nuint)sizeof(T) ? _segmentAlignment : (nuint)sizeof(T); + return new SegmentedMemoryHandle(ptr, byteLength, alignment, this); } /// @@ -651,7 +653,7 @@ T* ptr = AllocateWithAlignment(count, effectiveAlignment); nuint byteLength = (nuint)count * (nuint)sizeof(T); - return new SegmentedMemoryHandle(ptr, byteLength, this); + return new SegmentedMemoryHandle(ptr, byteLength, effectiveAlignment, this); } diff --git a/UnmangedMMU/WorkspaceHeap.cs b/UnmangedMMU/WorkspaceHeap.cs index 7f6a092..fbe21bc 100644 --- a/UnmangedMMU/WorkspaceHeap.cs +++ b/UnmangedMMU/WorkspaceHeap.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; using UnmanagedMMU.Allocators; using UnmanagedMMU.Handles; using UnmanagedMMU.Handles.Internal; @@ -33,6 +32,16 @@ /// public unsafe sealed class WorkspaceHeap : IDisposable, IUnmanagedMemoryOwner { + + /// + /// Struct defining metadata to be stored with each allocation + /// + private struct AllocationMetadata + { + public IntPtr Block; + public nuint Alignment; + } + /// /// The maximum size, in bytes, for "small" allocations. Uses fixed-size buckets /// @@ -61,12 +70,12 @@ /// Predefined bucket sizes used for small allocation reuse. /// private static readonly nuint[] _sizeClasses = - { + [ 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800, 832, 864, 896, 928, 960, 992, 1024 - }; + ]; /// /// Allocator interface used for all underlying unmanaged memory operations. @@ -84,17 +93,17 @@ /// /// For a given bucket, the corresponding stack contains previously allocated blocks that are available to be used /// - private readonly Dictionary> _smallFree = new(); + private readonly Dictionary> _smallFree = []; /// /// Free lists for medium allocations keyed by the exactly allocated size, in bytes, and sorted for best-fit. /// - private readonly SortedDictionary> _mediumFree = new(); + private readonly SortedDictionary> _mediumFree = []; /// /// Free lists for large allocations keyed by the exactly allocated size, in bytes, sorted for tolerance-based reuse. /// - private readonly SortedDictionary> _largeFree = new(); + private readonly SortedDictionary> _largeFree = []; /// /// Tracks the total bytes of memory reserved from the provided . @@ -116,33 +125,6 @@ /// private volatile bool _disposed; - /// - /// Internal header prepended to each allocation to track its size. - /// - [StructLayout(LayoutKind.Sequential)] - private struct BlockHeader - { - /// - /// Size of the allocation in bytes. - /// - public nuint Size; - - /// - /// Padding to ensure is 32-byte aligned - /// - private readonly nuint _pad1; - - /// - /// Padding to ensure is 32-byte aligned - /// - private readonly nuint _pad2; - - /// - /// Padding to ensure is 32-byte aligned - /// - private readonly nuint _pad3; - } - /// /// Creates a new . /// @@ -160,8 +142,10 @@ _allocator = allocator; // Initialize small-size buckets - foreach (var size in _sizeClasses) - _smallFree[size] = new Stack(); + foreach (nuint size in _sizeClasses) + { + _smallFree[size] = []; + } } /// @@ -243,16 +227,15 @@ /// Allocates a new block from the underlying allocator including a header. /// /// Requested payload size in bytes. + /// FOO /// /// Pointer to the allocated block (header included). /// - private IntPtr AllocateNew(nuint payloadSize) + private IntPtr AllocateNewAligned(nuint payloadSize, nuint alignment) { - nuint total = payloadSize + (nuint)sizeof(BlockHeader); - void* raw = _allocator.Alloc(total); - _totalReserved += total; + void* raw = _allocator.AllocAligned(payloadSize, alignment); + _totalReserved += payloadSize; _totalAllocations++; - return (IntPtr)raw; } @@ -264,100 +247,154 @@ /// An to the allocated memory. /// Thrown if heap is disposed. /// Thrown if size is zero. + /// The memory returned by this method is always 16-byte aligned, for other alignemnts use public IMemoryHandle Allocate(int count, bool zero = false) where T : unmanaged + { + return AllocateAligned(count, SegmentAlignment.Aligned16, zero); + } + + /// + /// + /// + /// + /// + /// + /// + /// + /// + public IMemoryHandle AllocateAligned(int count, SegmentAlignment alignment, bool zero = false) where T: unmanaged { ArgumentOutOfRangeException.ThrowIfNegative(count); ArgumentOutOfRangeException.ThrowIfZero(count); - if ((nuint)count > nuint.MaxValue / (nuint)(sizeof(T))) { throw new OverflowException($"Requested allocation of {count} elements of type {typeof(T)} exceeds allowable maximum memory size."); } nuint size = (nuint)count * (nuint)sizeof(T); + nuint requestedAlignment = (nuint)alignment; + nuint effectiveAlignment = requestedAlignment < (nuint)sizeof(T) ? (nuint)sizeof(T) : requestedAlignment; lock (_lock) { ThrowIfDisposed(); - void* ptr = null; - if (size <= _smallThreshold) - { - ptr = AllocateSmall(size, zero); - } - else if (size <= _mediumThreshold) - { - ptr = AllocateMedium(size, zero); - } - else - { - ptr = AllocateLarge(size, zero); - } + void* ptr; - return new PersistentMemoryHandle((T*)ptr, size, this); + if (size <= _smallThreshold) + ptr = AllocateSmallAligned(size, effectiveAlignment, zero); + else if (size <= _mediumThreshold) + ptr = AllocateMediumAligned(size, effectiveAlignment, zero); + else + ptr = AllocateLargeAligned(size, effectiveAlignment, zero); + + return new PersistentMemoryHandle((T*)ptr, size, effectiveAlignment, this); } } - /// Allocates a small-size block using bucketed free lists. - private void* AllocateSmall(nuint size, bool zero) + /// + /// TODO: Fill in + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int FindIndexAlignmentMatch(nuint alignment, List list) + { + // Attempt to find an index match the requested size + int mIdx = -1; + for (int i = 0; i < list.Count; i++) + { + if (list[i].Alignment >= alignment) + { + mIdx = i; + break; + } + } + return mIdx; + } + + /// + /// Allocates a small-size block using bucketed free lists. + /// + private void* AllocateSmallAligned(nuint size, nuint alignment, bool zero) { nuint bucket = GetSizeClass(size); - Stack stack = _smallFree[bucket]; + List free = _smallFree[bucket]; - IntPtr block = stack.Count > 0 - ? stack.Pop() - : AllocateNew(bucket); + // Attempt to find an index match the requested size + int mIdx = FindIndexAlignmentMatch(alignment, free); - BlockHeader* header = (BlockHeader*)block; - header->Size = bucket; + if (mIdx >= 0) + { + AllocationMetadata meta = free[mIdx]; + free.RemoveAt(mIdx); + _totalInUse += bucket; + void* user = (void*)meta.Block; + if (zero) + { + Unsafe.InitBlockUnaligned(user, 0, (uint)bucket); + } + return user; + } + + // allocate a new small block + IntPtr block = AllocateNewAligned(bucket, alignment); _totalInUse += bucket; - - void* user = header + 1; - + void* nUser = (void*)block; if (zero) - Unsafe.InitBlockUnaligned(user, 0, (uint)bucket); - - return user; + { + Unsafe.InitBlockUnaligned(nUser, 0, (uint)bucket); + } + return nUser; } /// Allocates a medium-size block using best-fit reuse. - private void* AllocateMedium(nuint size, bool zero) + private void* AllocateMediumAligned(nuint size, nuint alignment, bool zero) { - foreach (var kv in _mediumFree) + foreach (KeyValuePair> kv in _mediumFree) { if (kv.Key >= size && kv.Value.Count > 0) { - var block = kv.Value.Pop(); - var header = (BlockHeader*)block; - _totalInUse += header->Size; + List free = kv.Value; - void* user = header + 1; - if (zero) - Unsafe.InitBlockUnaligned(user, 0, (uint)header->Size); + // Attempt to find an index match the requested size + int mIdx = FindIndexAlignmentMatch(alignment, free); - return user; + if (mIdx >= 0) + { + AllocationMetadata meta = free[mIdx]; + free.RemoveAt(mIdx); + + _totalInUse += kv.Key; + void* user = (void*)meta.Block; + if (zero) + { + Unsafe.InitBlockUnaligned(user, 0, (uint)kv.Key); + } + return user; + } } } - - var newBlock = AllocateNew(size); - var newHeader = (BlockHeader*)newBlock; - newHeader->Size = size; + // no match + IntPtr newBlock = AllocateNewAligned(size, alignment); _totalInUse += size; - void* newUser = newHeader + 1; + void* nUser = (void*)newBlock; if (zero) { - Unsafe.InitBlockUnaligned(newUser, 0, (uint)size); - } - + Unsafe.InitBlockUnaligned(nUser, 0, (uint)size); + } - return newUser; + return nUser; } - /// Allocates a large block using tolerance-based reuse (smallest ≥ requested within waste bounds). - private void* AllocateLarge(nuint size, bool zero) + /// + /// Allocates a large block using tolerance-based reuse (smallest ≥ requested within waste bounds). + /// + private void* AllocateLargeAligned(nuint size, nuint alignment, bool zero) { - foreach (var kv in _largeFree) + foreach (KeyValuePair> kv in _largeFree) { nuint blockSize = kv.Key; if (blockSize < size || kv.Value.Count == 0) @@ -365,41 +402,46 @@ continue; } + // check waste tolerance before attempting to find an alignment match nuint waste = blockSize - size; - bool acceptable = - waste <= _largeMaxWasteBytes || - ((double)blockSize / size) <= _largeWasteRatioLimit; + bool acceptable = waste <= _largeMaxWasteBytes || ((double)blockSize / size) <= _largeWasteRatioLimit; if (!acceptable) { continue; } - var block = kv.Value.Pop(); - var header = (BlockHeader*)block; - _totalInUse += header->Size; + List free = kv.Value; - void* user = header + 1; - if (zero) + // Attempt to find an index match the requested size + int mIdx = FindIndexAlignmentMatch(alignment, free); + + if (mIdx >= 0) { - Unsafe.InitBlockUnaligned(user, 0, (uint)header->Size); - } + AllocationMetadata meta = free[mIdx]; + free.RemoveAt(mIdx); - return user; + _totalInUse += blockSize; + void* user = (void*)meta.Block; + if (zero) + { + Unsafe.InitBlockUnaligned(user, 0, (uint)blockSize); + } + + return user; + } } - var newBlock = AllocateNew(size); - var newHeader = (BlockHeader*)newBlock; - newHeader->Size = size; - _totalInUse += size; + // no match - void* newUser = newHeader + 1; + IntPtr block = AllocateNewAligned(size, alignment); + _totalInUse += size; + void* nUser = (void*)block; if (zero) { - Unsafe.InitBlockUnaligned(newUser, 0, (uint)size); + Unsafe.InitBlockUnaligned(nUser, 0, (uint)size); } - - return newUser; + return nUser; } /// @@ -421,23 +463,41 @@ lock (_lock) { - var header = ((BlockHeader*)handle.Pointer) - 1; - nuint size = header->Size; + nuint size = handle.ByteCount; + nuint alignment = handle.Alignment; _totalInUse -= size; if (size <= _smallThreshold) - _smallFree[size].Push((IntPtr)header); + { + _smallFree[size].Add(new AllocationMetadata + { + Block = (IntPtr)handle.Pointer, + Alignment = alignment + }); + } else if (size <= _mediumThreshold) { - if (!_mediumFree.TryGetValue(size, out var stack)) - _mediumFree[size] = stack = new Stack(); - stack.Push((IntPtr)header); + if (!_mediumFree.TryGetValue(size, out List? free)) + { + _mediumFree[size] = free = []; + } + free.Add(new AllocationMetadata + { + Block = (IntPtr)handle.Pointer, + Alignment = alignment + }); } else { - if (!_largeFree.TryGetValue(size, out var stack)) - _largeFree[size] = stack = new Stack(); - stack.Push((IntPtr)header); + if (!_largeFree.TryGetValue(size, out List? free)) + { + _largeFree[size] = free = []; + } + free.Add(new AllocationMetadata + { + Block = (IntPtr)handle.Pointer, + Alignment = alignment + }); } } } @@ -457,17 +517,17 @@ } /// Helper to free all blocks in a dictionary of free stacks. - private void PruneDictionary(IDictionary> dict) + private void PruneDictionary(IDictionary> dict) { - foreach (var kv in dict) + foreach (KeyValuePair> kv in dict) { - var stack = kv.Value; - while (stack.Count > 0) + List list = kv.Value; + while (list.Count > 0) { - var block = stack.Pop(); - var header = (BlockHeader*)block; - _allocator.Free((void*)block); - _totalReserved -= (header->Size + (nuint)sizeof(BlockHeader)); + var item = list[^1]; + list.RemoveAt(list.Count - 1); + _allocator.FreeAligned((void*)item.Block, item.Alignment); + _totalReserved -= kv.Key; } } }