# Allocators Every allocation in this library goes through an `AllocationHandle`. The handle determines where memory comes from, how it's organized, and when it's reclaimed. There is no default allocator — you always choose one explicitly. ## AllocationHandle `AllocationHandle` is a struct with a state pointer and three function pointers: ``` AllocationHandle _state : void* — allocator-specific context _alloc : delegate — allocate _realloc : delegate — reallocate _free : delegate — free ``` The function pointers let any allocator implementation be wrapped without boxing, virtual dispatch, or GC pressure. To create a custom allocator, you only need to populate this struct. Every collection stores the `AllocationHandle` it was created with and uses it for all internal memory operations. ## Built-in allocators The library ships with allocators managed by `AllocationManager`: | Allocator | Handle access | Backing | Lifetime | Reuse | Thread safety | |---|---|---|---|---|---| | Heap | `AllocationHandle.Persistent` | Heap (replaced by mimalloc if `MHP_ENABLE_MIMALLOC` is defined) | Until freed | Controlled by malloc or mimalloc | Yes | | FreeList | `AllocationHandle.FreeList` | Heap | Until freed | Yes — reuses freed blocks | Yes (remote-free queue) | | TLSF | `AllocationHandle.TLSF` | Virtual memory chunks | Until freed | Yes — low fragmentation | Yes (lock-protected) | | VirtualArena | `AllocationHandle.Temp` | Virtual memory (reserve on init, commit on demand) | Until `ResetTempAllocator()` | No — bulk reset only | Yes | | VirtualStack | `AllocationManager.CreateStackScope().AllocationHandle` | Virtual memory (reserve on init, commit on demand) | Via `VirtualStack.Scope` | Yes when scope disposed | No — thread-local | The library also provides heap-based variants of `Arena` and `Stack` for direct use via `MemoryPool`: | Allocator | Backing | Lifetime | Reuse | |---|---|---|---| | Arena | Heap (`NativeMemory.Alloc`) | Until `Reset()` or dispose | No — bulk reset only | | Stack | Heap (`NativeMemory.Alloc`) | Via `Stack.Scope` | Yes when scope disposed | ### VirtualArena (Temp) VirtualArena reserves a large virtual address range on initialization and commits physical memory on demand as allocations are made. Allocation bumps an offset pointer — there is no free-list walk, no block splitting, and no metadata search. This makes it the fastest allocator in the library. **Free is a no-op.** Individual frees do nothing. The entire arena is reset at once by calling `AllocationManager.ResetTempAllocator()`, which rewinds the offset back to zero. This makes the arena ideal for frame-scoped or phase-scoped work where you want to allocate freely and discard everything at once. ```csharp // Temp allocations are freed collectively. var a = new UnsafeArray(10, AllocationHandle.Temp); var b = new UnsafeList(AllocationHandle.Temp); // Reset everything. AllocationManager.ResetTempAllocator(); // Both a and b are now invalid. ``` Under the hood, `Temp` uses a `MemoryPool`. The arena uses 64 KB pages for OS-level commit granularity and is thread-safe with a lock-free bump-allocate path. ### FreeList (FreeList) The free-list allocator reclaims and reuses individual blocks. When you free a block, it is returned to a size-bucketed free list and can satisfy a future allocation of the same bucket. This avoids the overhead of re-committing virtual memory while keeping fragmentation low. FreeList uses per-thread caches for the hot path and a remote-free queue for cross-thread deallocation. This means threads can free each other's allocations without a global lock on the common path. ```csharp // FreeList allocations can be freed and reused independently. var a = new UnsafeArray(10, AllocationHandle.FreeList); a.Dispose(); // Memory goes back to the free list. var b = new UnsafeArray(10, AllocationHandle.FreeList); // Likely backed by the same memory as a. ``` ### TLSF (Persistent) The Two-Level Segregated Fit allocator guarantees O(1) allocation and deallocation with very low external fragmentation. It organizes free blocks by size class in a two-level bitmap index, which lets it find a best-fit block in constant time. TLSF backs its memory pool with virtual memory chunks allocated via `Mmap`. `AllocationHandle.Persistent` maps to the manager's internal TLSF allocator. Use it for long-lived allocations where fragmentation matters and where you need consistent O(1) performance. The TLSF implementation is single-threaded internally and wrapped in a lock by the manager. For concurrent use from multiple threads, access is serialized through that lock. ```csharp // Persistent (TLSF) for long-lived allocations. var cache = new UnsafeHashMap( AllocationHandle.Persistent ); // ... use for the lifetime of the application ... cache.Dispose(); ``` ### VirtualStack (Stack) VirtualStack is a LIFO allocator backed by a reserved virtual address range that commits physical memory on demand. It allocates by bumping an offset (like VirtualArena) but adds a scope mechanism that rewinds to a saved position on dispose. The stack is **not thread-safe** and is designed for single-threaded or thread-local contexts. Each thread gets its own stack through `AllocationManager.CreateStackScope()`: ```csharp // Creates a thread-local stack scope. // The scope's AllocationHandle can be used for allocations // that are automatically reclaimed when the scope ends. using var scope = AllocationManager.CreateStackScope(); // Allocate from the stack. var temp = new UnsafeArray(10, scope.AllocationHandle); // When scope is disposed, all allocations are rewound. ``` The stack uses 64 KB commit granularity and supports scope nesting. The scope records the offset at creation and rewinds to it on dispose — allocations from an inner scope are always reclaimed before the outer scope. ### Arena (heap) `Arena` is the heap-based counterpart of `VirtualArena`. It uses `NativeMemory.Alloc` to allocate a fixed-size buffer on the heap and bumps an offset pointer for allocations. It supports the same bulk-reset pattern but without virtual memory reservation. ```csharp using var arena = new MemoryPool( new Arena.CreationOptions { size = 1024 * 1024 }); var arr = new UnsafeArray(10, arena.AllocationHandle); // Reset rewinds the offset. Memory stays allocated. arena.Allocator.Reset(); ``` `Arena` is thread-safe with a lock-free bump-allocate path. ### Stack (heap) `Stack` is the heap-based counterpart of `VirtualStack`. It uses `NativeMemory.Alloc` to allocate a fixed-size buffer on the heap and uses the same scope mechanism to rewind allocations on scope dispose. ```csharp using var stack = new MemoryPool( new Stack.CreationOptions { size = 1024 * 1024 }); using (var scope = stack.Allocator.CreateScope(stack.AllocationHandle)) { var arr = new UnsafeArray(10, scope.AllocationHandle); } // Scope dispose rewinds all allocations. ``` `Stack` is **not thread-safe** and is designed for single-threaded contexts. ## AllocationManager configuration `AllocationManager` can be configured with an `AllocationManagerDesc` to control the capacity and alignment of each built-in allocator: ```csharp var desc = new AllocationManagerDesc { ArenaCapacity = 1024 * 1024 * 1024, // 1 GB virtual reservation StackCapacity = 32 * 1024 * 1024, // 32 MB per thread FreeListChunkSize = 64 * 1024, // 64 KB chunks FreeListDefaultAlignment = 16, // 16-byte alignment TLSFAlignment = 16, // 16-byte alignment TLSFInitialChunkSize = 64 * 1024 * 1024 // 64 MB initial chunk }; AllocationManager.Initialize(desc); ``` Calling `Initialize()` with no arguments uses these same defaults. ## MemoryPool for scoped allocators `MemoryPool` creates a standalone allocator outside of `AllocationManager`. This is useful when you want: - An allocator type not available in the built-in set - An allocator scoped to a single method or algorithm - Isolation from the global allocation state ```csharp using var pool = new MemoryPool( new TLSF.CreationOptions { alignment = 16, initialChunkSize = 1024 * 1024 }); using var array = new UnsafeArray(10, pool.AllocationHandle); // When pool is disposed, all TLSF memory is released. ``` The pool wraps any type implementing `IMemoryAllocator`. This includes `Arena`, `VirtualArena`, `Stack`, `VirtualStack`, `TLSF`, and `DynamicArena`: ```csharp // Heap-based arena. using var arenaPool = new MemoryPool( new Arena.CreationOptions { size = 1024 * 1024 }); // Virtual-memory-based stack. using var stackPool = new MemoryPool( new VirtualStack.CreationOptions { reserveCapacity = 1024 * 1024 }); // Dynamically growing arena (heap). using var dynamicPool = new MemoryPool( new DynamicArena.CreationOptions { initialSize = 4096 }); ``` `DynamicArena` creates linked arenas that grow automatically when full, with no virtual address reservation upfront. ## Custom allocators Creating a custom allocator requires populating an `AllocationHandle` with your own allocate, reallocate, and free functions: ```csharp static void* MyAlloc(void* state, nuint size, nuint alignment, AllocationOption option) { // Your allocation logic. } static void* MyRealloc(void* state, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption option) { // Your reallocation logic. } static void MyFree(void* state, void* ptr) { // Your deallocation logic. } var handle = new AllocationHandle( myAllocatorState, &MyAlloc, &MyRealloc, &MyFree ); var array = new UnsafeArray(10, handle); ``` For more structured custom allocators, implement `IMemoryAllocator` and use `MemoryPool`: ```csharp public unsafe struct MyAllocator : IMemoryAllocator { public struct CreationOptions { /* ... */ } public static MyAllocator Create(in CreationOptions opts) { /* ... */ } public void* Allocate(nuint size, nuint alignment, AllocationOption option) { /* ... */ } public void* Reallocate(void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption option) { /* ... */ } public void Free(void* ptr) { /* ... */ } public void Dispose() { /* ... */ } } using var pool = new MemoryPool(/* ... */); ``` ## AllocationOption `AllocationOption` is a flags enum that controls per-allocation behavior: | Value | Behavior | |---|---| | `None` | Memory is returned as-is, contents are undefined | | `Clear` | All allocated bytes are zeroed before returning | ```csharp // Request zeroed memory. var ptr = handle.Alloc(1024, 16, AllocationOption.Clear); ``` `Clear` is useful for security-sensitive data or when you need deterministic initialization. Omitting it avoids the cost of touching every page. ## Enable Mimalloc You can define `MHP_ENABLE_MIMALLOC` to use mimalloc as the underlying allocator for `AllocationHandle.Persistent` and `MemoryUtility.Malloc` instead of the default C allocator. > Using mimalloc requires to install the `TerraFX.Interop.Mimalloc` package. ## Additional resources - [Introduction](introduction.md) — install, first steps, and safety checks - [Architecture overview](architecture-overview.md) — layering, MemoryHandle, and struct semantics - [Collection types](collection-types.md) — all available data structures