namespace UnmanagedMMU
{
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using UnmanagedMMU.Allocators;
using UnmanagedMMU.Diagnostics;
using UnmanagedMMU.Handles;
using UnmanagedMMU.Handles.Internal;
///
/// Represents configurable alignment requirements for memory segments and allocations.
/// Values are powers of 2 and reflect common hardware requirements (SIMD, cache lines, native pointer size).
///
public enum SegmentAlignment
{
///
/// 8-byte alignment. Minimum for 64-bit pointers and primitives (long, double).
///
Aligned8 = 8,
///
/// 16-byte alignment. Required for Vector128 (SSE/NEON).
/// Common default for general-purpose SIMD workloads.
///
Aligned16 = 16,
///
/// 32-byte alignment. Required for Vector256 (AVX).
/// Recommended default for SIMD-heavy applications.
///
Aligned32 = 32,
///
/// 64-byte alignment. Matches standard CPU cache-line size.
/// Ensures segment bases align to cache line boundaries, minimizing cache-line splits.
///
Aligned64 = 64,
///
/// 128-byte alignment.
/// Advanced optimization for specific cache-aware algorithms or AVX-512 contexts.
///
Aligned128 = 128
}
///
/// Implementation of segmented Bump-Allocator.
///
/// This implementation manages fixed-sized unmanaged memory segments.
/// This ensures allocations are fast and contiguous within a segment.
/// Once a segment is full, a new one is automatically allocated
///
public unsafe sealed class SegmentedPool : IDisposable, IUnmanagedMemoryOwner
{
///
/// The default size for a
///
private const nuint _defaultSegmentSize = 4194304; // 4 MiB
///
/// The size that a new should be
///
private nuint _currentSegmentSize;
///
/// The alignment that each new should be on
///
private readonly nuint _segmentAlignment;
///
/// Queue of free segments
///
private readonly Stack _freeSegments = new();
///
/// List of active memory segments
///
private readonly List _activeSegments = [];
///
/// Tracks the total bytes of memory reserved from the provided
///
private nuint _totalReserved = 0;
///
/// Tracks the total amount of allocated bytes currently in use
///
private nuint _totalUsed = 0;
///
/// Internal lock, ensures thread safety while maintaining a simple interface
///
private readonly Lock _lock = new();
///
/// Indicates whether this has been disposed.
///
private volatile bool _disposed;
///
/// Pointer to the currently in use
///
private Segment* _current;
///
/// Allocator interface used for all underlying unmanaged memory operations.
///
private readonly IUnmanagedAllocator _allocator;
///
/// Indicates if allocations from the pool should be zeroed.
///
private readonly bool _zeroMemory;
///
/// Represents a memory segment in the .
///
///
///
/// - Pointer to the start of the unmanaged memory block
/// - indicates the current allocation position within the segment. Each new allocation advances this offset by the number of bytes allocated.
/// - is the total size, in bytes, of the segment.
/// - Segments are managed internally by the and should not be modified directly outside of the pool.
/// - A is a contiguous block of unmanaged memory from which allocations are served sequentially via bump allocation.
///
///
private struct Segment
{
///
/// Pointer to the start of the unmanaged memory block.
///
public byte* Ptr;
///
/// The current offset into the where the next allocation will occur.
///
public nuint Offset;
///
/// Total size of the in bytes.
///
public nuint Size;
}
///
/// Readonly snapshot of the current pool state for diagnostics.
///
public readonly struct PoolState
{
///
/// The configured segment alignment setting for this pool instance.
/// This is the MINIMUM alignment requirement for the segment base.
///
public nuint SegmentAlignment { get; init; }
///
/// The size, in bytes, of each segment allocated by the pool.
///
public nuint SegmentSize { get; init; }
///
/// Total bytes currently reserved from the system.
///
public nuint TotalReserved { get; init; }
///
/// Total bytes currently used by allocations.
///
public nuint TotalUsed { get; init; }
///
/// Current active segment base address.
///
public nuint CurrentBase { get; init; }
///
/// Current offset within the segment.
///
public nuint CurrentOffset { get; init; }
///
/// Checks if the current segment base is aligned to the configured setting.
///
public bool BaseAligned { get; init; }
///
/// Number of active segments currently being used.
///
public int ActiveSegmentCount { get; init; }
///
/// Number of recycled segments available for reuse.
///
public int FreeSegmentCount { get; init; }
///
/// Total number of segments in the pool (Active + Free).
///
public int TotalSegmentCount { get; init; }
///
/// Bytes lost to alignment padding vs actual data in current segment.
/// Computed as: CurrentOffset - TotalUsedBytes.
///
public nuint PaddingBytes { get; init; }
///
/// Memory that could be freed if Trim() is called with default args (minFreeSegments: 16).
///
public nuint PotentialSavings { get; init; }
public string Suggestion { get; init; }
}
///
/// Readonly snapshot of a specific segment.
///
public readonly struct SegmentInfo
{
///
/// Segment index within the active list.
///
public int Index { get; init; }
///
/// Base address of the segment (actual unmanaged memory pointer).
///
public nuint BaseAddress { get; init; }
///
/// Current offset usage (bytes used since segment reset).
///
public nuint UsedBytes { get; init; }
///
/// Total capacity of the segment.
///
public nuint Size { get; init; }
///
/// Indicates if this is the currently active segment.
///
public bool IsActive { get; init; }
///
/// The alignment requirement for the segment base itself.
///
public nuint AlignmentRequirement { get; init; }
///
/// True if is a multiple of .
///
public bool IsAligned { get; init; }
public override string ToString()
{
bool aligned = IsAligned;
string status = IsActive ? "CURRENT" : "INACTIVE";
double usagePercent = Size > 0 ? ((double)UsedBytes / Size) * 100 : 0;
string alignStatus = aligned ? "Aligned" : "Misaligned";
return $" Segment #{Index} [{status}]\n" +
$" Base Address: 0x{(nuint)BaseAddress:x}\n" +
$" Size: {FormatBytes(Size)}\n" +
$" Used: {FormatBytes(UsedBytes)} ({usagePercent:F2}%)\n" +
$" Base Alignment: {AlignmentRequirement} bytes - {alignStatus}";
}
private static string FormatBytes(nuint bytes)
{
if (bytes >= 1073741824) return $"{bytes / 1073741824} GiB";
if (bytes >= 1048576) return $"{bytes / 1048576} MiB";
if (bytes >= 1024) return $"{bytes / 1024} KiB";
return $"{bytes} B";
}
}
///
/// Initializes a new with the specified default and count
///
/// Size of each segment in bytes (default 4 MiB)
/// Alignment requirement for each allocated segment and . Must be a power of 2 (Default 32)
/// Number of segments to pre-allocate to the pool
/// When true, memory returned from the pool is zero-initialized.
/// When false, memory may contain previously used data and it is the caller's responsibility to clear it if required.
///
/// Thrown if is zero, or if is less than 1.
///
public SegmentedPool(nuint segmentSize = _defaultSegmentSize, SegmentAlignment segmentAlignment = SegmentAlignment.Aligned16, int initialSegments = 4, bool zeroMemory = false)
: this(segmentSize, segmentAlignment, initialSegments, zeroMemory, new DefaultUnmanagedAllocator())
{
}
///
/// Initializes a new with the specified default and count
///
/// Size of each segment in bytes (default 4 MiB)
/// Alignment requirement for each allocated segment. Must be a power of 2 (Default 32)
/// Number of segments to pre-allocate to the pool
/// When true, memory returned from the pool is zero-initialized.
/// When false, memory may contain previously used data and it is the caller's responsibility to clear it if required.
/// IUnmanagedAllocator instance that implements the allocator
///
/// Thrown if is zero, or if is less than 1.
///
internal SegmentedPool(nuint segmentSize, SegmentAlignment segmentAlignment, int initialSegments, bool zeroMemory, IUnmanagedAllocator allocator)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(segmentSize);
ArgumentOutOfRangeException.ThrowIfLessThan(initialSegments, 1);
_allocator = allocator;
_currentSegmentSize = segmentSize;
_segmentAlignment = (nuint)segmentAlignment;
_zeroMemory = zeroMemory;
Segment* seg;
// Pre-allocate segments
for (int i = 0; i < initialSegments; i++)
{
seg = AllocateNewSegment(_currentSegmentSize);
ZeroSegment(seg);
_freeSegments.Push((IntPtr)seg);
}
_current = (Segment*)_freeSegments.Pop();
_activeSegments.Add((IntPtr)_current);
}
///
/// Gets the number of free segments available for use in the pool.
///
/// The number of free segments.
public int FreeSegmentCount
{
get
{
lock (_lock)
{
return _freeSegments.Count;
}
}
}
///
/// Gets the number of segments currently in use in the pool.
///
/// The number of currently active Segments.
public int ActiveSegmentCount
{
get
{
lock (_lock)
{
return _activeSegments.Count;
}
}
}
///
/// Returns the total number of bytes that have currently been allocated.
///
/// The total number of bytes that have been allocated.
public nuint TotalAllocatedBytes
{
get
{
lock (_lock)
{
return _totalReserved;
}
}
}
///
/// Gets the total number of bytes currently in use across all active segments.
///
/// The total number of bytes that are in use.
public nuint TotalUsedBytes
{
get
{
lock (_lock)
{
return _totalUsed;
}
}
}
///
/// Gets the size, in bytes, used when allocating new instances.
///
///
/// This reflects the most recently configured size and affects only future allocations.
///
public nuint CurrentSegmentSize
{
get { return _currentSegmentSize; }
}
///
/// Gets a value indicating whether this instance has been disposed.
///
/// True, if the current instance has been disposed of, false otherwise.
///
/// Once disposed, further calls to allocation or reset methods will throw .
///
public bool IsDisposed
{
get { return _disposed; }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static nuint AlignUp(nuint value, nuint alignment)
{
return (value + alignment - 1) & ~(alignment - 1);
}
///
/// Zeroes the memory if the is configured to do so.
/// Called whenever a segment becomes active for use (new or reused).
///
/// Pointer to the Segment struct to initialize.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ZeroSegment(Segment* segment)
{
if (_zeroMemory)
{
Unsafe.InitBlockUnaligned(segment->Ptr, 0, (uint)segment->Size);
}
}
///
/// Allocates a block of unmanaged memory of size for elements of type and returns a pointer to the allocated memory.
///
/// The unmanaged value type to store in the allocated memory. Must be a struct or primitive type.
/// The number of elements of type to allocate.
/// A pointer to the first element of the allocated unmanaged memory block. The memory is valid until the is reset or disposed.
///
///
/// - This allocation is performed in unmanaged memory and bypasses the .NET garbage collector.
/// - Accessing the memory after or has been called is undefined behavior and may lead to crashes.
///
///
///
/// Thrown if is less than or equal to zero.
///
///
/// Thrown if the total allocation size (count * sizeof(T)) exceeds the maximum allowable size.
///
private T* Alloc(int count) where T : unmanaged
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(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 bytes = (nuint)(count * sizeof(T));
nuint alignment = _segmentAlignment > (nuint)sizeof(T) ? _segmentAlignment : (nuint)sizeof(T);
lock (_lock)
{
ThrowIfDisposed();
nuint currentPtr = (nuint)_current->Ptr + _current->Offset;
nuint alignedPtr = AlignUp(currentPtr, alignment);
nuint alignedOffset = alignedPtr - (nuint)_current->Ptr;
// Check space INCLUDING padding
if (alignedOffset + bytes > _current->Size)
{
SwitchSegment(bytes);
// Recalcuate from new the base of the new segment
currentPtr = (nuint)_current->Ptr;
alignedPtr = AlignUp(currentPtr, alignment);
alignedOffset = alignedPtr - (nuint)_current->Ptr;
}
T* ptr = (T*)(_current->Ptr + alignedOffset);
_current->Offset = alignedOffset + bytes;
_totalUsed += bytes;
return ptr;
}
}
private T* AllocateWithAlignment(int count, nuint alignment) where T : unmanaged
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(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 bytes = (nuint)(count * sizeof(T));
lock (_lock)
{
ThrowIfDisposed();
nuint currentPtr = (nuint)_current->Ptr + _current->Offset;
nuint alignedPtr = AlignUp(currentPtr, alignment);
nuint alignedOffset = alignedPtr - (nuint)_current->Ptr;
if (alignedOffset + bytes > _current->Size)
{
SwitchSegment(bytes);
currentPtr = (nuint)_current->Ptr;
alignedPtr = AlignUp(currentPtr, alignment);
alignedOffset = alignedPtr - (nuint)_current->Ptr;
}
T* ptr = (T*)(_current->Ptr + alignedOffset);
_current->Offset = alignedOffset + bytes;
_totalUsed += bytes;
return ptr;
}
}
///
/// Switches to a new when the current is full.
///
/// The number of bytes required for the upcoming allocation. If the current does not have enough free space, a new will be used.
private void SwitchSegment(nuint requiredBytes)
{
Segment* segment;
// Allocate fresh Segment if needed
if (_freeSegments.Count == 0 || requiredBytes > _currentSegmentSize)
{
segment = AllocateNewSegment(requiredBytes > _currentSegmentSize ? requiredBytes : _currentSegmentSize);
}
else
{
segment = (Segment*)_freeSegments.Pop();
segment->Offset = 0;
}
ZeroSegment(segment);
_activeSegments.Add((IntPtr)segment);
_current = segment;
}
///
/// Allocates a new
///
/// Size, in bytes, for the new .
/// A pointer to the newly allocated
private Segment* AllocateNewSegment(nuint size)
{
byte* ptr = (byte*)_allocator.AllocAligned(size, _segmentAlignment);
// Allocate metadata struct with its natural alignment (8 bytes for 64-bit nuint)
Segment* segment = (Segment*)_allocator.AllocAligned((nuint)sizeof(Segment), 8);
segment->Ptr = ptr;
segment->Offset = 0;
segment->Size = size;
_totalReserved += size;
return segment;
}
///
/// Sets the current size used for subsequent allocations.
///
///
/// The new segment size, in bytes, that will be used when allocating future instances.
/// Must be greater than zero.
///
///
/// This does not affect any that have already been allocated or are currently active.
/// Only new created after calling this method will use the updated size.
///
///
/// Thrown if is zero.
///
public void SetSegmentSize(nuint newSize)
{
ThrowIfDisposed();
if (newSize == 0)
{
throw new ArgumentOutOfRangeException(nameof(newSize), "Segment size must be greater than zero.");
}
_currentSegmentSize = newSize;
}
///
/// Resets the current size back to the default 4 MiB
///
///
/// Future allocations will revert to using this default size
/// Active and free are not modified.
///
public void ResetSegmentSize()
{
ThrowIfDisposed();
_currentSegmentSize = _defaultSegmentSize;
}
///
/// Allocates a block of unmanaged memory of size for elements of type and returns a handle representing the allocation.
///
/// The unmanaged value type to store in the allocated memory. Must be a struct or primitive type.
/// The number of elements of type to allocate.
/// A representing the allocated memory. The handle is valid until either or is called on this .
///
///
/// - This allocation is performed in unmanaged memory and bypasses the .NET garbage collector.
/// - Accessing the memory after or has been called is undefined behavior.
///
///
///
/// Thrown if is less than or equal to zero.
///
///
/// Thrown if the total allocation size (count * sizeof(T)) exceeds the maximum allowable size.
///
public IMemoryHandle Allocate(int count) where T : unmanaged
{
T* ptr = Alloc(count);
nuint byteLength = (nuint)count * (nuint)sizeof(T);
nuint alignment = _segmentAlignment > (nuint)sizeof(T) ? _segmentAlignment : (nuint)sizeof(T);
return new SegmentedMemoryHandle(ptr, byteLength, alignment, this);
}
///
/// Allocates a block of unmanaged memory of size for elements of type with the specified and returns a handle representing the allocation.
///
/// The unmanaged value type to store in the allocated memory. Must be a struct or primitive type.
/// The number of elements of type to allocate.
/// The alignment to aliign the allocation to inside of the currently active
/// A representing the allocated memory. The handle is valid until either or is called on this .
///
///
/// - This allocation is performed in unmanaged memory and bypasses the .NET garbage collector.
/// - Accessing the memory after or has been called is undefined behavior.
///
///
///
/// Thrown if is less than or equal to zero.
///
///
/// Thrown if the total allocation size (count * sizeof(T)) exceeds the maximum allowable size.
///
public IMemoryHandle AllocateAligned(int count, SegmentAlignment alignment) where T : unmanaged
{
nuint requestedAlignment = (nuint)alignment;
nuint typeSize = (nuint)sizeof(T);
// Ensure alignment is at least the size of T (never under-align for types)
nuint effectiveAlignment = requestedAlignment < typeSize ? typeSize : requestedAlignment;
T* ptr = AllocateWithAlignment(count, effectiveAlignment);
nuint byteLength = (nuint)count * (nuint)sizeof(T);
return new SegmentedMemoryHandle(ptr, byteLength, effectiveAlignment, this);
}
///
/// Frees unused in the free pool, reducing unmanaged memory usage.
///
/// Minimum number of free segments to retain for future allocations. Defaults to 16.
///
/// Segments beyond the retained count will have their unmanaged memory released back to the system.
///
///
/// Thrown if is negative.
///
public void Trim(int minFreeSegments = 16)
{
ThrowIfDisposed();
ArgumentOutOfRangeException.ThrowIfNegative(minFreeSegments, nameof(minFreeSegments));
lock (_lock)
{
while (_freeSegments.Count > minFreeSegments)
{
var ip = _freeSegments.Pop();
Segment* segment = (Segment*)ip;
// Free the unmanaged memory
_allocator.FreeAligned(segment->Ptr, _segmentAlignment);
_totalReserved -= segment->Size;
_allocator.FreeAligned(segment, 8);
}
}
}
///
/// Resets the allocator, returning all active segments to the free pool.
///
/// If true, trims the in the free pool.
public void Reset(bool trim = false)
{
ThrowIfDisposed();
lock (_lock)
{
foreach (var ip in _activeSegments)
{
Segment* segment = (Segment*)ip;
segment->Offset = 0;
_freeSegments.Push(ip);
// Zero memory if requested
ZeroSegment(segment);
}
_activeSegments.Clear();
_totalUsed = 0;
if (_freeSegments.Count > 0)
{
_current = (Segment*)_freeSegments.Pop();
_activeSegments.Add((IntPtr)_current);
}
else
{
// This should not be hit in normal circumstances as we always have _current
_current = AllocateNewSegment(_currentSegmentSize);
_activeSegments.Add((IntPtr)_current);
// Zero newly allocated segment if requested
ZeroSegment(_current);
}
// Optionally trim excess free segments after reset
if (trim)
{
Trim();
}
}
}
///
/// Frees
///
///
///
void IUnmanagedMemoryOwner.Free(IOwnedHandle handle)
{
ThrowIfDisposed();
if (handle.Pointer == null)
{
return;
}
if (handle.GetOwner() != this)
{
throw new InvalidOperationException(
"Attempted to free a handle from a different allocator pool.");
}
//
}
///
/// Releases all unmanaged memory allocated by the and clears internal state.
/// After calling this method, the pool can no longer be used for allocations.
///
///
/// After disposal, all allocations become invalid and any further operations will throw .
/// This method is thread-safe and may be called multiple times safely.
///
public void Dispose()
{
lock (_lock)
{
if (_disposed)
{
return;
}
// Free active segments
foreach (var ip in _activeSegments)
{
Segment* segment = (Segment*)ip;
_allocator.FreeAligned(segment->Ptr, _segmentAlignment);
_allocator.FreeAligned(segment, 8);
}
// Free free segments
foreach (var ip in _freeSegments)
{
Segment* segment = (Segment*)ip;
_allocator.FreeAligned(segment->Ptr, _segmentAlignment);
_allocator.FreeAligned(segment, 8);
}
_activeSegments.Clear();
_freeSegments.Clear();
_current = null;
_totalReserved = 0;
_totalUsed = 0;
_disposed = true;
}
}
///
/// Throws an if the has already been disposed.
///
///
/// Thrown when this instance is no longer valid for use.
///
private void ThrowIfDisposed()
{
ObjectDisposedException.ThrowIf(_disposed, this);
}
///
/// Gets a snapshot of the current pool state for diagnostics.
/// Thread-safe and produces no garbage.
///
/// A containing current pool metrics.
public PoolState GetPoolState()
{
ThrowIfDisposed();
lock (_lock)
{
nuint alignment = _segmentAlignment;
nuint basePtr = (nuint)_current->Ptr;
int active = _activeSegments.Count;
int free = _freeSegments.Count;
int total = active + free;
// 1. Calculate Padding (Alignment Overhead) for Current Segment
// This is dynamic: Offset tracks total bytes written, TotalUsed tracks actual allocation bytes
nuint padding = _current->Offset - _totalUsed;
// 2. Calculate Potential Savings (Trim Projection)
nuint potentialSavings = 0;
if (free > 16)
{
int excess = free - 16;
potentialSavings = (nuint)excess * _currentSegmentSize;
}
return new PoolState
{
SegmentAlignment = alignment,
SegmentSize = _currentSegmentSize,
TotalReserved = _totalReserved,
TotalUsed = _totalUsed,
CurrentBase = basePtr,
CurrentOffset = _current->Offset,
BaseAligned = (basePtr & (alignment - 1)) == 0,
ActiveSegmentCount = active,
FreeSegmentCount = free,
TotalSegmentCount = total,
PaddingBytes = padding,
PotentialSavings = potentialSavings
};
}
}
///
/// Generates a diagnostic report for the pool.
/// Thread-safe and produces no garbage.
///
/// A formatted diagnostic string.
public string GetDiagnosticReport()
{
PoolState state = GetPoolState();
DiagnosticConfig config = new()
{
SegmentSize = _currentSegmentSize,
TotalReserved = _totalReserved
};
string suggestion = SegmentedPoolDiagnostics.GenerateSuggestions(state, config);
state = state with { Suggestion = suggestion };
return SegmentedPoolDiagnostics.GenerateReport(state);
}
///
/// Helper method to construct a from raw segment data.
///
/// Pointer to the segment to inspect.
/// Pointer to the currently active segment for comparison.
/// The logical index of the segment in the list.
/// A populated struct.
private SegmentInfo CreateSegmentInfo(Segment* segment, Segment* current, int index)
{
nuint alignment = _segmentAlignment;
nuint ptr = (nuint)segment->Ptr;
bool isAligned = (ptr & (alignment - 1)) == 0;
return new SegmentInfo
{
Index = index,
BaseAddress = ptr,
UsedBytes = segment->Offset,
Size = segment->Size,
IsActive = (segment == current),
AlignmentRequirement = alignment,
IsAligned = isAligned
};
}
///
/// Gets information about the currently active segment.
/// This is the primary diagnostic view for memory usage within the active segment.
///
/// A for the current segment, or null if disposed.
public SegmentInfo GetCurrentSegmentInfo()
{
ThrowIfDisposed();
lock (_lock)
{
return CreateSegmentInfo(_current, _current, 0);
}
}
///
/// Gets a list of all segment details for deep diagnostics.
/// Includes both active and free segments.
///
/// A list of containing all segments.
public List GetAllSegmentInfos()
{
ThrowIfDisposed();
lock (_lock)
{
var totalSegments = _activeSegments.Count + _freeSegments.Count;
var result = new List(totalSegments);
int currentIndex = 0;
for (int i = 0; i < _activeSegments.Count; i++)
{
IntPtr ip = _activeSegments[i];
Segment* segment = (Segment*)ip;
result.Add(CreateSegmentInfo(segment, _current, currentIndex));
currentIndex++;
}
foreach (var ip in _freeSegments)
{
Segment* segment = (Segment*)ip;
result.Add(CreateSegmentInfo(segment, _current, currentIndex++));
}
return result;
}
}
}
}