|
1 | | -# MemoryPrototype |
| 1 | +# ⚡ MemoryManager: High-Performance Unmanaged Arena Allocator for .NET |
2 | 2 |
|
3 | | -**MemoryPrototype** is a high-performance, polymorphic unmanaged memory arena and handle framework built to bypass the .NET Garbage Collector for latency-critical runtime environments. Inspired by data-oriented, sharded architectures in modern low-latency game engines and production-grade runtimes, it implements stable handle indirection, generational version validation, zero-allocation custom structures, and drop-in memory lane strategies for both single-threaded and concurrent parallel execution profiles. |
| 3 | +[](License.txt) |
| 4 | +[](https://dotnet.microsoft.com/) |
| 5 | +[]() |
4 | 6 |
|
5 | | -> ⚠️ **Engineering Status:** This is a polished **systems-engineering sandbox and architecture laboratory**. While fully verified by automated parallel chaos test suites to achieve exceptional throughput and zero GC allocation on hot paths, it is meant for specialized standalone subsystems. See the [Production Finish-Line Roadmap](#-the-production-finish-line-roadmap) for remaining considerations. |
| 7 | +**MemoryManager** is a low-level, production-grade memory arena architecture engineered for high-throughput, low-latency .NET applications such as game engine cores, real-time telemetry systems, database engines, and networking pipelines. |
| 8 | + |
| 9 | +By managing native heap memory directly through platform allocators, **MemoryManager completely eliminates .NET Garbage Collector (GC) pressure**, guarantees CPU cache-line locality, and provides safe, generational handle tracking with in-place adaptive compaction. |
6 | 10 |
|
7 | 11 | --- |
8 | 12 |
|
9 | | -### 🚀 Key Features |
| 13 | +## 🏛️ System Architecture |
| 14 | + |
| 15 | +The framework relies on a **Tiered Dual-Lane Memory Architecture** backed by an indirection handle tracking system. |
| 16 | + |
| 17 | +``` |
| 18 | + ┌─────────────────────────────────────────┐ |
| 19 | + │ MemoryArena / ConcurrentArena │ |
| 20 | + └────────────────────┬────────────────────┘ |
| 21 | + │ |
| 22 | + ┌─────────────────┴─────────────────┐ |
| 23 | + ▼ ▼ |
| 24 | + ┌─────────────────────┐ ┌─────────────────────┐ |
| 25 | + │ FastLane │ │ SlowLane │ |
| 26 | + │ (Hot / Short-Lived)│ │ (Cold / Long-Lived)│ |
| 27 | + └──────────┬──────────┘ └──────────┬──────────┘ |
| 28 | + │ │ |
| 29 | + ┌──────────────┼──────────────┐ ├─────────────────────┐ |
| 30 | + ▼ ▼ ▼ ▼ ▼ |
| 31 | +┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────────┐ ┌──────────────┐ |
| 32 | +│ SlabLane │ │LinearLane │ │ FastLane │ │ BlobManager │ │ FreeList │ |
| 33 | +│ (Slabs) │ │ (Bump) │ │(FreeList) │ │ (Size <=256B) │ │ (Size >256B) │ |
| 34 | +└───────────┘ └───────────┘ └───────────┘ └───────────────┘ └──────────────┘ |
| 35 | +``` |
| 36 | + |
| 37 | +### Key Architectural Pillars |
| 38 | + |
| 39 | +* **Generational Handles (`MemoryHandle`):** Memory references are abstract versioned handles (`Id` + `Version`). Handles prevent Use-After-Free bugs and allow memory compaction without invalidating caller references. |
| 40 | +* **Pluggable Allocation Strategies:** Choose between **Slab Bins**, **Linear Bump**, or **Variable Free-Lists** depending on workload characteristics. |
| 41 | +* **Lock-Free Concurrency:** `ConcurrentMemoryArena` shards allocations across thread-local fast lanes with atomic cross-thread deallocation staging queues. |
| 42 | +* **In-Place Adaptive Compaction:** Compaction slides active memory blocks *in-place* using binary memory copies without doubling the RAM footprint or forcing full-heap defragmentation stalls. |
10 | 43 |
|
11 | | -1. **Stable Handle Indirection (Relocatable Memory)** |
12 | | - Instead of handing out raw native pointers that permanently pin layout blocks in memory, the system yields opaque, lightweight $O(1)$ `MemoryHandle` tokens. This layer of indirection permits the background lanes to safely relocate data during defragmentation sweeps without ever breaking or invalidating external user references. |
| 44 | +--- |
13 | 45 |
|
14 | | -2. **Generational Version Validation (Zombie Protection)** |
15 | | - To completely eliminate the dangling pointer and double-free vulnerabilities common in unmanaged environments, each `MemoryHandle` carries an index `Id` and an atomic generation `Version`. When handles are recycled, their internal slots increment their generation counts. Legacy handles attempting to access stale memory addresses instantly trigger safe access exceptions instead of silently corrupting recycled data blocks. |
| 46 | +## ⚡ Key Features |
16 | 47 |
|
17 | | -3. **Sharded Thread Isolation (`ConcurrentMemoryArena`)** |
18 | | - Achieves true scaling across high-core CPUs by providing an isolated, lock-free allocation path for every independent worker thread via `ThreadLocal<IFastLane>` storage. Workers allocate and recycle space at raw hardware speeds without fighting over a global execution lock. Cross-thread deallocations (Thread B freeing Thread A's memory) are offloaded asynchronously via atomic lock-free concurrent queues. |
| 48 | +* 🚀 **Zero GC Overhead:** Allocations reside entirely on the unmanaged heap (`Marshal.AllocHGlobal` / `NativeMemory`). |
| 49 | +* 🔒 **Zombie Handle Protection:** Version-stamped handles instantly detect and throw on access to deallocated memory blocks. |
| 50 | +* 🛡️ **Canary Guard Bands:** Debug builds feature pre- and post-guard band validation (`0xDEADBEEF`) to catch buffer underruns and overruns immediately. |
| 51 | +* 📦 **Zero-Allocation Collections:** Includes `ArenaList<T>`, `ArenaQueue<T>`, `ArenaBuffer<T>`, and `ArenaStack<T>` equipped with struct `Span<T>.Enumerator` support for zero-allocation `foreach` loops. |
| 52 | +* 🎯 **Policy-Based Maintenance:** Background Janitor maintenance automatically promotes aging or cold data out of fast lanes into the slow lane. |
19 | 53 |
|
20 | | -4. **Dynamic Segregated Size Classes (`AllocatorStrategy.Slab`)** |
21 | | - Introduces an ultra-fast, zero-compaction fast-lane implementation. Slabs dynamically generate power-of-two size tracks (e.g., 16B, 32B, 64B... up to the configured boundary) matching your hardware layout. Memory allocation and deallocation within a slab are reduced to instantaneous $O(1)$ LIFO index array stack operations—completely eliminating external fragmentation. |
| 54 | +--- |
22 | 55 |
|
23 | | -5. **The "Janitor" (Automated Tiered Eviction)** |
24 | | - Data lifespans are managed policy-heuristically. Hot data lives in your choice of fast-lane architecture. If an allocation outstays its welcome (frame age limits), grows past entry thresholds, or is explicitly initialized via `AllocationHints.Cold`, the automated Janitor moves it downstream to a bulk `SlowLane` during maintenance frames, instantly deploying a seamless routing redirection stub in its wake. |
| 56 | +## 🚀 Getting Started |
25 | 57 |
|
26 | | -6. **Unified Polymorphic Framework (`IMemoryAllocator`)** |
27 | | - Both `MemoryArena` (optimized for single-threaded isolated pipelines) and `ConcurrentMemoryArena` (built for massive multi-threaded scaling) implement the `IMemoryAllocator` interface contract. This allows all syntactic sugar, array helpers, type initializers, and string engines to be shared seamlessly across both execution environments. |
| 58 | +### 1. Basic Allocation & Typed Storage |
28 | 59 |
|
29 | | ---- |
| 60 | +```csharp |
| 61 | +using MemoryManager.Core; |
| 62 | +using MemoryManager.Lanes; |
| 63 | + |
| 64 | +// 1. Initialize configuration |
| 65 | +var config = new MemoryManagerConfig |
| 66 | +{ |
| 67 | + FastLaneSize = 1024 * 1024, // 1 MB Hot Path |
| 68 | + SlowLaneSize = 4 * 1024 * 1024, // 4 MB Cold Path |
| 69 | + FastLaneStrategy = AllocatorStrategy.FreeList |
| 70 | +}; |
30 | 71 |
|
31 | | -## 📐 Architecture Overview |
| 72 | +// 2. Instantiate Arena |
| 73 | +using var arena = new MemoryArena(config); |
32 | 74 |
|
33 | | -The system enforces a strict hierarchical topology over specialized unmanaged memory tiers. |
| 75 | +// 3. Store a struct directly in unmanaged memory |
| 76 | +var handle = arena.Store(new Vector3 { X = 10.0f, Y = 20.0f, Z = 30.0f }); |
34 | 77 |
|
35 | | -* **⚡ FastLane**: Optimized for high-frequency, short-lived "hot" transient structures. Uses sequential **positive integers** (`1, 2, 3...`) for allocation IDs. Version tracking is backed by an ultra-fast, growable native pointer array (`uint*`) that maps handle IDs directly to generation counts via an $O(1)$ array offset calculation. |
36 | | -* **🐌 SlowLane**: Optimized for large, persistent "cold" allocations or long-lived structural blocks. Uses sequential **negative integers** (`-1, -2, -3...`) for allocation IDs. Maps negative handles cleanly to positive array coordinates using an absolute value index converter (`-id`), retaining raw pointer lookup performance. |
37 | | -* **🪐 BlobManager (Slow-Lane Sub-Allocator)**: Optimized for isolating tiny, chaotic allocations (e.g., $\le$ 256 bytes) away from the primary free block table to prevent memory fragmentation. |
38 | | -* **走 OneWayLane (The Bridge)**: Orchestrates seamless data migration. It safely reads the original entry metadata to pull telemetry (preserving original frame age, priorities, and usage hints) and executes a vector-speed `System.Buffer.MemoryCopy` across the lane boundaries before converting the old site into a routing redirection stub. |
39 | | -* **🧱 SlabLane (Segregated Bins)**: An alternative drop-in fast-lane strategy optimized for ultra-fast, uniform object pooling (e.g., ECS components, entities). It partitions unmanaged memory into distinct power-of-two size classes (16B, 32B, 64B, 128B, etc.). Both allocations and frees execute as instant $O(1)$ LIFO slot recycling operations, completely immunizing the hot path against external fragmentation and bypassing compaction stutters entirely. |
40 | | ---- |
| 78 | +// 4. Read back the struct |
| 79 | +var position = arena.Get<Vector3>(handle); |
41 | 80 |
|
42 | | -## 🧩 Config Presets & Advanced Usage |
| 81 | +// 5. Free memory when done |
| 82 | +arena.Free(handle); |
| 83 | +``` |
43 | 84 |
|
44 | | -Configurations can be customized manually or generated via optimized static factories targeted at distinct architecture types: |
| 85 | +### 2. High-Performance Zero-GC `ArenaList<T>` |
45 | 86 |
|
46 | 87 | ```csharp |
47 | | -using MemoryManager; |
48 | | -using MemoryManager.Core; |
| 88 | +using MemoryManager.Types; |
| 89 | + |
| 90 | +// Create an unmanaged resizable list backed by the arena allocator |
| 91 | +using var list = new ArenaList<int>(arena, initialCapacity: 16); |
49 | 92 |
|
50 | | -// --- PROFILE 1: Ultra-Fast Real-Time Game Loop (Single Threaded Pipeline) --- |
51 | | -// Employs a blazing fast O(1) Linear Bump allocator for transient per-frame arrays. |
52 | | -var gameConfig = MemoryManagerConfig.CreateForGameLoop(totalBudget: 32 * 1024 * 1024); |
53 | | -var physicsArena = new MemoryArena(gameConfig); |
| 93 | +list.Add(100); |
| 94 | +list.Add(200); |
| 95 | +list.Add(300); |
54 | 96 |
|
55 | | -// --- PROFILE 2: High-Velocity Concurrent Object Pooling (Multi-Threaded Scaling) --- |
56 | | -// Provisions lock-free sharded Slab Lanes across all cores utilizing uniform size class bins. |
57 | | -var poolConfig = MemoryManagerConfig.CreateForObjectPooling(totalBudget: 64 * 1024 * 1024); |
58 | | -var parallelArena = new ConcurrentMemoryArena(poolConfig); |
| 97 | +// Fast, allocation-free iteration using Span<T>.Enumerator |
| 98 | +foreach (ref var value in list) |
| 99 | +{ |
| 100 | + Console.WriteLine(value); |
| 101 | +} |
| 102 | +``` |
59 | 103 |
|
60 | | -## Basic CRUD Operations & Extension Methods |
| 104 | +### 3. Circular Ring Buffer `ArenaQueue<T>` |
61 | 105 |
|
62 | 106 | ```csharp |
63 | | -using MemoryManager; |
64 | | -using MemoryManager.Core; |
| 107 | +using MemoryManager.Types; |
65 | 108 |
|
66 | | -// Syntactic sugar extensions run seamlessly on any IMemoryAllocator instance |
67 | | -IMemoryAllocator allocator = new ConcurrentMemoryArena(MemoryManagerConfig.CreateForObjectPooling()); |
| 109 | +using var queue = new ArenaQueue<Point>(arena, initialCapacity: 32); |
68 | 110 |
|
69 | | -// 1. Structural Primitive Allocation & Direct Value Writing |
70 | | -var healthHandle = allocator.Store(100); |
| 111 | +queue.Enqueue(new Point { X = 1, Y = 2 }); |
| 112 | +queue.Enqueue(new Point { X = 3, Y = 4 }); |
71 | 113 |
|
72 | | -// 2. High-Performance Fixed-Size Arrays |
73 | | -var arrayHandle = allocator.AllocateArray<int>(count: 5); |
74 | | -int[] sourceData = { 10, 20, 30, 40, 50 }; |
| 114 | +var point = queue.Dequeue(); |
| 115 | +``` |
75 | 116 |
|
76 | | -// Executes atomic, size-validated bitwise memory copy |
77 | | -allocator.BulkSet(arrayHandle, sourceData); |
| 117 | +--- |
78 | 118 |
|
79 | | -// 3. Ultra-Fast Zero-Allocation Spans (Hot Path Loops) |
80 | | -// Extract the span ONCE outside loops to execute modifications at raw hardware cache speed |
81 | | -Span<int> activeSpan = allocator.GetSpan<int>(arrayHandle, sourceData.Length); |
82 | | -for (int i = 0; i < activeSpan.Length; i++) |
83 | | -{ |
84 | | - activeSpan[i] *= 2; |
85 | | -} |
| 119 | +## 📐 Allocation Strategies Comparison |
| 120 | + |
| 121 | +| Strategy | Allocation Time | Deallocation Time | Fragmentation | Best Use Case | |
| 122 | +| :--- | :---: | :---: | :---: | :--- | |
| 123 | +| **`Slab`** | $O(1)$ | $O(1)$ | Zero (Bin-local) | Fixed-size uniform struct pools | |
| 124 | +| **`LinearBump`** | $O(1)$ | $O(1)$ (Bulk) | N/A | High-speed per-frame scratch buffers | |
| 125 | +| **`FreeList`** | $O(N)$ Best/First-Fit | $O(1)$ Coalescing | Variable | Dynamic, unpredictable payload sizes | |
| 126 | + |
| 127 | +--- |
| 128 | + |
| 129 | +## 🧹 Adaptive In-Place Compaction |
| 130 | + |
| 131 | +The `SlowLane` provides two defragmentation policies via `CompactionStyle`: |
| 132 | + |
| 133 | +```csharp |
| 134 | +// Full Compaction: Complete defragmentation (Ideal for loading boundaries) |
| 135 | +slowLane.Compact(CompactionStyle.Full); |
| 136 | + |
| 137 | +// GoodEnough Compaction: Stops immediately once a gap of 4KB is opened (Ideal for frame-time budget preservation) |
| 138 | +slowLane.Compact(CompactionStyle.GoodEnough, requiredSize: 4096); |
| 139 | +``` |
| 140 | + |
| 141 | +* **In-Place Memory Sliding:** Compaction slides active memory blocks down using `System.Buffer.MemoryCopy` without allocating temporary secondary buffers. |
| 142 | +* **Automatic Healing:** If `Allocate()` fails due to fragmented space, `SlowLane` automatically triggers an in-place compaction pass before retrying. |
| 143 | + |
| 144 | +--- |
| 145 | + |
| 146 | +## 🧪 Testing & Verification |
| 147 | + |
| 148 | +The solution contains comprehensive unit, correctness, stress, and performance test suites covering: |
| 149 | + |
| 150 | +* **Correctness:** Handle generation, stub redirection, and canary overrun/underrun validation. |
| 151 | +* **Compaction:** In-place data sliding, `GoodEnough` early termination, and `FreeMany` batch recovery. |
| 152 | +* **Stability:** Multi-threaded stress tests under concurrent alloc/free workloads. |
| 153 | + |
| 154 | +To execute the full test suite: |
| 155 | +```bash |
| 156 | +dotnet test --configuration Release |
| 157 | +``` |
| 158 | + |
| 159 | +--- |
86 | 160 |
|
87 | | -// 4. Symmetric Unmanaged UTF-8 String Storage (Bypasses GC Allocation 완전히) |
88 | | -var stringHandle = allocator.StoreString("Hello from the native unmanaged Arena!"); |
89 | | -string decodedText = allocator.GetString(stringHandle); |
| 161 | +## 📜 License |
90 | 162 |
|
91 | | -// 5. Clean Resource Reclamation |
92 | | -allocator.Free(healthHandle); |
93 | | -allocator.Free(stringHandle); |
94 | | -``` |
| 163 | +This project is licensed under the Apache 2.0 License - see the [License.txt](License.txt) file for details. |
0 commit comments