UnmanagedMMU

UnmanagedMMU is a high-performance C# memory manager library that provides efficient unmanaged memory allocation.


Table of Contents


IMemoryHandle

The UnmanagedMMU namespace provides two interface variations for memory handles. The non-generic IMemoryHandle is a thin wrapper around a pointer for untyped access. The generic IMemoryHandle extends the non-generic interface to provide strong typing and implements IDisposable for automatic resource management.


IMemoryHandle (Untyped)

The IMemoryHandle interface represents a non-generic memory handle. It provides access to the raw pointer and byte count, useful when the element type is unknown at compile time or when interfacing with low-level APIs.

Key Properties:

Property Type Description
Pointer void* A raw pointer to the start of the allocation.
ByteCount nuint The total size of the allocation in bytes.

IMemoryHandle<T> (Typed)

The IMemoryHandle<T> interface extends IMemoryHandle and provides strongly typed access to the underlying memory. It is the primary interface used with SegmentedPool allocations. This interface implements IDisposable.

Key Properties:

Property Type Description
Pointer T* A typed pointer to the start of the allocation.
ByteCount nuint The total size of the allocation in bytes.
Length nuint The number of T elements in the allocation (ByteCount / sizeof(T)).

Example Usage:

using var pool = new SegmentedPool();

// Allocate memory for 100 integers
IMemoryHandle<int> handle = pool.Allocate<int>(100);

try
{
    // Access via typed pointer
    unsafe
    {
        for (int i = 0; i < handle.Length; i++)
        {
            handle.Pointer[i] = i;
        }
    }
}
finally
{
    handle.Dispose();
}

SegmentedPool

SegmentedPool is a segmented bump allocator for unmanaged memory in C#. It pre-allocates memory in fixed-size segments and serves allocations sequentially within each segment. Once a segment is full, the pool automatically switches to a new segment, allowing fast, contiguous allocations without fragmentation.

Advantages of the SegmentedPool:

  • High performance: Allocation is simple pointer arithmetic.
  • Contiguous memory: Reduces cache misses and improves data locality.
  • Thread-safe: Supports concurrent allocation operations.
  • Manual memory control: Works outside of .NET GC, ideal for high-performance or low-latency scenarios.
  • Configurable alignment: Supports SIMD and hardware-specific alignment requirements.

Segments

A Segment is a contiguous block of unmanaged memory managed by the pool. Each segment contains:

Field Type Description
Ptr byte* Pointer to the start of the unmanaged memory block.
Offset nuint Current allocation offset within the segment. Increases as memory is allocated.
Size nuint Total size of the segment in bytes.

Segments are allocated automatically by the pool and should never be modified outside the pool.


SegmentAlignment

The SegmentAlignment enum defines the alignment requirements for memory segments. Values are powers of 2 and reflect common hardware requirements (SIMD, cache lines, native pointer size).

Value Alignment Description
Aligned8 8 bytes Minimum for 64-bit pointers and primitives (long, double).
Aligned16 16 bytes Required for Vector128 (SSE/NEON). Common default for general-purpose SIMD workloads.
Aligned32 32 bytes Required for Vector256 (AVX). Recommended default for SIMD-heavy applications.
Aligned64 64 bytes Matches standard CPU cache-line size. Ensures segment bases align to cache line boundaries, minimizing cache-line splits.
Aligned128 128 bytes Advanced optimization for specific cache-aware algorithms or AVX-512 contexts.

Default: SegmentAlignment.Aligned32


Allocation Strategy

The SegmentedPool uses a bump allocator strategy:

  1. Memory is allocated sequentially within the current Segments.
  2. Offset is incremented with each allocation.
  3. When the current segment does not have enough space, the pool switches to a new Segments (either from the free pool or a freshly allocated one).
  4. Allocations are aligned according to the SegmentAlignment or a requested alignment.

This provides O(1) allocation performance for most operations.


Constructor

Initializes a new instance of the SegmentedPool with the specified segment size, alignment, and number of pre-allocated Segments.

Syntax

// Default configuration
SegmentedPool pool = new SegmentedPool();

// Custom parameters
SegmentedPool pool = new SegmentedPool(
    segmentSize: 4 * 1024 * 1024,
    segmentAlignment: SegmentAlignment.Aligned32,
    initialSegments: 4,
    zeroMemory: false
);

Parameters

Parameter Type Description
segmentSize nuint Size of each Segment in bytes. Optional. Defaults to 4 MiB (4 * 1024 * 1024 bytes).
segmentAlignment SegmentAlignment Alignment requirement for each allocated Segment. Must be a power of 2. Optional. Defaults to Aligned32.
initialSegments int Number of Segments to pre-allocate in the pool. Optional. Defaults to 4.
zeroMemory bool 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. Optional. Defaults to false.

Return value

Returns a new instance of the SegmentedPool with the specified Segments size and number of pre-allocated Segments.

Exceptions

Exception Condition
ArgumentOutOfRangeException Thrown if segmentSize is zero or initialSegments is less than 1.
ArgumentException Thrown if alignment is not a power of 2.

Remarks

The SegmentedPool pre-allocates the specified number of Segments during construction, ensuring that the pool can immediately serve allocations without additional memory allocation overhead. The pool operates in unmanaged memory and bypasses the .NET garbage collector, so all memory must be manually released by calling Dispose() when the pool is no longer needed.

When zeroMemory is set to true, all memory returned from the pool is zero-initialized, which ensures that no residual data from previous allocations is exposed. When zeroMemory is false, memory may contain leftover data from prior use, and it is the caller's responsibility to clear it if necessary to maintain data integrity or security. Setting zeroMemory to true may have a slight performance cost due to the additional initialization step.


Allocation Methods

The SegmentedPool provides two allocation methods, both returning IMemoryHandle<T> for safe memory management.

Allocate

Allocates a block of unmanaged memory of the specified count for elements of type T using the pool's default segment alignment.

Syntax
IMemoryHandle<T> handle = pool.Allocate<T>(count);
Parameters
Parameter Type Description
count int Number of elements of type T to allocate. Must be greater than zero.
Return value

An IMemoryHandle<T> representing the allocated memory. The handle is valid until either Reset or Dispose is called on the pool.

Remarks
  • This allocation is performed in unmanaged memory and bypasses the .NET garbage collector.
  • Accessing the memory after Reset or Dispose has been called is undefined behavior.
  • Memory alignment is based on the pool's configured SegmentAlignment.
Exceptions
Exception Condition
ArgumentOutOfRangeException Thrown if count is less than or equal to zero.
OverflowException Thrown if the total allocation size exceeds the maximum allowable size.
ObjectDisposedException Thrown if the pool has been disposed.
Example
using var pool = new SegmentedPool();
IMemoryHandle<byte> handle = pool.Allocate<byte>(100);

try
{
    // Access memory through typed pointer
    unsafe
    {
        for (int i = 0; i < handle.Length; i++)
        {
            handle.Pointer[i] = i;
        }
    }
}
finally
{
    handle.Dispose(); // Returns memory to pool
}

AllocateAligned

Allocates a block of unmanaged memory with the specified alignment requirement. This is useful for SIMD or hardware-specific operations.

Syntax
IMemoryHandle<T> handle = pool.AllocateAligned<T>(count, alignment);
Parameters
Parameter Type Description
count int Number of elements of type T to allocate. Must be greater than zero.
alignment SegmentAlignment The alignment to align the allocation to inside the currently active Segment.
Return value

An IMemoryHandle<T> representing the allocated memory. The handle is valid until either Reset or Dispose is called on the pool.

Remarks
  • This allocation is performed in unmanaged memory and bypasses the .NET garbage collector.
  • Accessing the memory after Reset or Dispose has been called is undefined behavior.
  • The alignment requirement must be a power of 2.
  • If the requested alignment is less than sizeof(T), the type's natural size is used instead.
Exceptions
Exception Condition
ArgumentOutOfRangeException Thrown if count is less than or equal to zero.
OverflowException Thrown if the total allocation size exceeds the maximum allowable size.
ObjectDisposedException Thrown if the pool has been disposed.
ArgumentException Thrown if alignment is not a power of 2.
Example
using var pool = new SegmentedPool(segmentAlignment: SegmentAlignment.Aligned32);
IMemoryHandle<UInt32> handle = pool.AllocateAligned<Vector256>(64, SegmentAlignment.Aligned32);

try
{
    unsafe
    {
        // Perform SIMD operations on aligned memory
        for (int i = 0; i < handle.Length; i++)
        {
            // Process aligned SIMD data
        }
    }
}
finally
{
    handle.Dispose();
}

Pool State Properties

The SegmentedPool provides several read-only properties for monitoring pool state:

CurrentSegmentSize

Gets the size, in bytes, used when allocating new Segment instances.

Syntax
nuint size = pool.CurrentSegmentSize;
Return value

The size, in bytes, used when allocating new Segment.

Remarks

This reflects the most recently configured size and affects only future segment allocations.


TotalAllocatedBytes

Gets the total number of bytes that have currently been allocated from the system.

Syntax
nuint allocated = pool.TotalAllocatedBytes;
Return value

The total number of bytes allocated from the system.

Remarks

This includes memory for all active and free Segments.


TotalUsedBytes

Gets the total number of bytes currently in use across all active segments.

Syntax
nuint used = pool.TotalUsedBytes;
Return value

The total number of bytes currently in use.

Remarks

This represents the actual data bytes allocated, excluding alignment padding.

ActiveSegmentCount

Gets the number of segments currently in use in the pool.

Syntax
int count = pool.ActiveSegmentCount;
Return value

The number of segments that are currently active and not yet returned to the free pool.

Remarks

This property is thread-safe and reflects the current state of the pool.


FreeSegmentCount

Gets the number of free segments available for reuse in the pool.

Syntax
int count = pool.FreeSegmentCount;
Return value

The number of segments that are currently free and available for reuse.

Remarks

This property is thread-safe. A value of 0 indicates that the pool will need to allocate a new segment for the next allocation.


IsDisposed

Gets a value indicating whether this instance has been disposed.

Syntax
bool isDisposed = pool.IsDisposed;
Return value

true if the pool has been disposed; otherwise false.

Remarks

Once this value is true, any further calls to allocation or management methods will throw a ObjectDisposedException.


Segment Size Management

SetSegmentSize

Sets the Segment size to use for future allocations.

Syntax
pool.SetSegmentSize(newSize);
Parameters
Parameter Type Description
newSize nuint The new segment size in bytes. Must be > 0.
Return value

None.

Remarks

This method will only affect the Segment size of future allocations; it does not modify existing Segments.

Exceptions
Exception Condition
ArgumentOutOfRangeException Thrown if newSize is zero.
ObjectDisposedException Thrown if the pool has been disposed.

ResetSegmentSize

Resets the Segment size for future allocations back to the default (4 MiB).

Syntax
pool.ResetSegmentSize();
Parameters

None.

Return value

None.

Remarks

This method will only affect the Segment size of future allocations; it does not modify existing Segments.


Reset and Trim

Reset

Resets the pool, returning all active Segments to the free pool for reuse.

Syntax
pool.Reset(trim: false);
Parameters
Parameter Type Description
trim bool Optional. If true, trims excess free Segments after reset. Defaults to false
Return value

None.

Remarks

This method resets all active Segments offsets to zero. Additionally, no memory is freed unless trim is true; the pool retains free Segments for future allocations.


Trim

Frees unused Segments in the free pool, reducing memory usage.

Syntax
pool.Trim(minFreeSegments: 16);
Parameters
Parameter Type Description
minFreeSegments int Minimum number of free Segments to retain. Segments beyond this count are released. Defaults to 16.
Return value

None.

Remarks

This method releases unmanaged memory from Segments beyond the specified minimum. This is useful for helping to control the unmanaged memory footprint in long-running applications.

Exceptions
Exception Condition
ArgumentOutOfRangeException Thrown if minFreeSegments is negative.
ObjectDisposedException Thrown if the pool has been disposed.

Diagnostics

The SegmentedPool provides several diagnostic methods for monitoring pool health and efficiency.

GetPoolState

Gets a snapshot of the current pool state for diagnostics.

Syntax
SegmentedPool.PoolState state = pool.GetPoolState();
Return value

A SegmentedPool.PoolState struct containing current pool metrics.

Example
var state = pool.GetPoolState();
Console.WriteLine($"Active: {state.ActiveSegmentCount}, Free: {state.FreeSegmentCount}");
Console.WriteLine($"Efficiency: {(100.0 * state.TotalUsed / (state.TotalUsed + state.PaddingBytes)):F0}%");

GetDiagnosticReport

Generates a diagnostic report for the pool including actionable suggestions.

Syntax
string report = pool.GetDiagnosticReport();
Return value

A formatted diagnostic string.

Example
string report = pool.GetDiagnosticReport();
// Output:
// === SegmentedPool Diagnostics ===
//   Configuration
//     Segment Alignment:   32 bytes (Min Base Alignment)
//     Segment Size:        4 MiB
//   Segment Summary
//     Total Segments:      5 (4 active, 1 free)
//     Potential Savings:   0 (via Trim())
//   Current Segment
//     Base Address:        0x1a2b3c4d
//     Offset:              1048576 bytes
//     Base Alignment:      OK (To 32 bytes)
//   Memory Statistics
//     Total Reserved:      16 MiB
//     Total Used:          8 MiB
//     Efficiency:          80% (Good)
//     Padding Overhead:    2 MiB
//   Allocation Breakdown
//     Data Bytes:          8 MiB
//     Alignment Padding:   2 MiB
//     Total Segment Space: 10 MiB
//   Action Required: Pool operating normally.

GetCurrentSegmentInfo

Gets information about the currently active segment. This is the primary diagnostic view for memory usage within the active segment.

Syntax
SegmentedPool.SegmentInfo info = pool.GetCurrentSegmentInfo();
Return value

A SegmentedPool.SegmentInfo struct for the current segment.


GetAllSegmentInfos

Gets a list of all segment details for deep diagnostics. Includes both active and free segments.

Syntax
List<SegmentedPool.SegmentInfo> allSegments = pool.GetAllSegmentInfos();
Return value

A list of SegmentedPool.SegmentInfo structs containing all segments.


Dispose

Releases all unmanaged memory used by the pool and clears internal state.

Syntax

pool.Dispose();

Parameters

None.

Return value

None.

Remarks

This method frees all active and free Segments so that after disposal, any memory allocated from the pool is invalid. This method is safe to call multiple times and calling any other public method after disposal is also safe.

After disposal, all allocations become invalid and any further operations will throw ObjectDisposedException.

Description
A C# Memory manager that provides a Segmented Memory Pool and Persistent pool
Readme 99 KiB
Languages
C# 100%