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;
}
}
}