|
| 1 | +# ⚛️ AtomAlloc ⚛️ |
| 2 | + |
| 3 | +[](https://github.com/ovnanova/atomalloc/actions/workflows/rust.yml) [](https://www.rust-lang.org) |
| 4 | + |
| 5 | +An experimental async-first memory allocator exploring atomic & lock-free allocation patterns in Rust. |
| 6 | + |
| 7 | +> **Research Implementation**: This is an experimental allocator focused on exploring async memory management patterns. It is NOT optimized for production use and currently exhibits higher overhead than traditional allocators. |
| 8 | +
|
| 9 | +## Overview |
| 10 | + |
| 11 | +AtomAlloc investigates the intersection of async Rust, atomic operations, and memory management by implementing a fully lock-free, task-aware allocator. While performance is not yet competitive with production allocators, it provides insights into async allocation patterns and challenges. |
| 12 | + |
| 13 | +## Design Goals vs Reality |
| 14 | + |
| 15 | +### ✅ Successfully Achieved |
| 16 | +- 🛡️ **Zero Unsafe Code**: Fully safe Rust implementation |
| 17 | +- 🔓 **Lock-Free Design**: Atomic operations throughout |
| 18 | +- 💾 **Task Awareness**: Generations and task-local caching |
| 19 | +- 🔄 **Async Interface**: Full async/await support |
| 20 | + |
| 21 | +### ❌ Current Limitations |
| 22 | +- **Performance**: slower than system allocator for common cases |
| 23 | +- **Memory Overhead**: higher memory usage due to atomic metadata |
| 24 | +- **Cache Efficiency**: poor cache locality from atomic operations |
| 25 | +- **Cold Start**: initial allocation overhead from block initialization |
| 26 | +- **Compatibility**: incompatible with GlobalAlloc trait or the unstable allocator_api feature |
| 27 | + |
| 28 | +## Usage |
| 29 | + |
| 30 | +If you'd like to experiment with this allocator, download this repo. |
| 31 | + |
| 32 | +Basic usage: |
| 33 | + |
| 34 | +```rust |
| 35 | +// Create allocator instance |
| 36 | +let alloc = AtomAlloc::new().await; |
| 37 | + |
| 38 | +// Allocation |
| 39 | +let layout = Layout::new::<[u8; 1024]>(); |
| 40 | +let block = alloc.allocate(layout).await?; |
| 41 | + |
| 42 | +// Write data |
| 43 | +block.write(0, &[1, 2, 3, 4]).await?; |
| 44 | + |
| 45 | +// Read data |
| 46 | +let data = block.read(0, 4).await?; |
| 47 | + |
| 48 | +// Deallocation |
| 49 | +alloc.deallocate(block).await; |
| 50 | + |
| 51 | +// Get allocation stats |
| 52 | +let stats = alloc.stats().await; |
| 53 | +println!("Cache hit rate: {}%", |
| 54 | + stats.cache_hits as f64 / (stats.cache_hits + stats.cache_misses) as f64 * 100.0); |
| 55 | +``` |
| 56 | + |
| 57 | +## Configuration |
| 58 | + |
| 59 | +The allocator can be configured via `AtomAllocConfig`: |
| 60 | + |
| 61 | +```rust |
| 62 | +let config = AtomAllocConfig { |
| 63 | + max_memory: 1024 * 1024 * 1024, // 1GB |
| 64 | + max_block_size: 64 * 1024, // 64KB |
| 65 | + min_block_size: 64, // 64B |
| 66 | + alignment: 16, |
| 67 | + cache_ttl: Duration::from_secs(300), |
| 68 | + max_caches: 1000, |
| 69 | + initial_pool_size: 1024 * 1024, // 1MB |
| 70 | + zero_on_dealloc: true, |
| 71 | +}; |
| 72 | + |
| 73 | +let alloc = AtomAlloc::with_config(config).await; |
| 74 | +``` |
| 75 | + |
| 76 | +## Technical Architecture |
| 77 | + |
| 78 | +### Core Components |
| 79 | + |
| 80 | +```rust |
| 81 | +pub struct AtomAlloc { |
| 82 | + pool: Arc<MemoryPool>, |
| 83 | + cache: Arc<BlockCache>, |
| 84 | + block_manager: Arc<BlockManager>, |
| 85 | + stats: Arc<AtomAllocStats>, |
| 86 | + config: Arc<AtomAllocConfig>, |
| 87 | +} |
| 88 | + |
| 89 | +pub struct Block { |
| 90 | + state: AtomicU64, // Packs generation + flags |
| 91 | + size: AtomicUsize, // Block size |
| 92 | + data: Box<[AtomicU8]>, // Actual memory storage |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +### Memory Model |
| 97 | + |
| 98 | +Allocation follows a three-tier hierarchy: |
| 99 | +1. Block Cache with Hot/Cold Queues |
| 100 | +2. Size Class Pool with Power-of-2 Classes |
| 101 | +3. Global Memory Pool |
| 102 | + |
| 103 | +Each level uses atomic operations and lock-free data structures for synchronization. |
| 104 | + |
| 105 | +### Generation Safety |
| 106 | + |
| 107 | +```rust |
| 108 | +impl BlockManager { |
| 109 | + pub async fn verify_generation(&self, block: &Pin<Arc<Block>>) -> Result<(), AtomAllocError> { |
| 110 | + let block_gen = block.generation(); |
| 111 | + let current_gen = self.current_generation.load(Ordering::Acquire); |
| 112 | + |
| 113 | + if block_gen > current_gen { |
| 114 | + return Err(AtomAllocError::BlockError(BlockError::InvalidGeneration { |
| 115 | + block: block_gen, |
| 116 | + expected: current_gen, |
| 117 | + })); |
| 118 | + } |
| 119 | + Ok(()) |
| 120 | + } |
| 121 | +} |
| 122 | +``` |
| 123 | + |
| 124 | +Memory safety is enforced through a generation system that tracks block validity. |
| 125 | + |
| 126 | +## Critical Implementation Challenges |
| 127 | + |
| 128 | +### 1. Cache Efficiency |
| 129 | + |
| 130 | +The block cache implements a hot/cold queue system to balance reuse and memory pressure: |
| 131 | + |
| 132 | +```rust |
| 133 | +pub struct BlockCache { |
| 134 | + manager: Arc<BlockManager>, |
| 135 | + pool: Arc<MemoryPool>, |
| 136 | + size_classes: Vec<Arc<SizeClass>>, |
| 137 | + stats: Arc<AtomAllocStats>, |
| 138 | +} |
| 139 | +``` |
| 140 | + |
| 141 | +### 2. Memory Ordering |
| 142 | + |
| 143 | +Ensuring correct ordering without locks requires careful atomic operations: |
| 144 | + |
| 145 | +```rust |
| 146 | +pub struct Block { |
| 147 | + pub async fn write(&self, offset: usize, data: &[u8]) -> Result<(), BlockError> { |
| 148 | + let size = self.size.load(Ordering::Acquire); |
| 149 | + // Atomic writes with proper ordering |
| 150 | + self.state.fetch_and(!ZEROED_FLAG, Ordering::Release); |
| 151 | + Ok(()) |
| 152 | + } |
| 153 | +} |
| 154 | +``` |
| 155 | + |
| 156 | +### 3. Zero-on-Free Overhead |
| 157 | + |
| 158 | +Memory zeroing for security has performance implications: |
| 159 | + |
| 160 | +```rust |
| 161 | +async fn zero_block(&self, block: &Pin<Arc<Block>>) { |
| 162 | + if self.config.zero_on_dealloc { |
| 163 | + block.clear().await; |
| 164 | + } |
| 165 | +} |
| 166 | +``` |
| 167 | + |
| 168 | +## Further Improvements |
| 169 | + |
| 170 | +Current areas of investigation: |
| 171 | +1. Cache-friendly atomic operations |
| 172 | +2. Improved size class distribution |
| 173 | +3. Memory coalescing techniques |
| 174 | +4. Alternative cache hierarchies |
| 175 | +5. Reduce generation verification overhead |
| 176 | + |
| 177 | +## Contributing |
| 178 | + |
| 179 | +This is an experiment that I would like to improve further over time. |
| 180 | + |
| 181 | +I welcome contributions that would: |
| 182 | +- Explore new async allocation patterns |
| 183 | +- Improve performance characteristics |
| 184 | + |
| 185 | +## License |
| 186 | + |
| 187 | +[](LICENSE) |
| 188 | + |
| 189 | +## Why This Exists |
| 190 | + |
| 191 | +This allocator serves as an investigation into several questions: |
| 192 | +- Can we build a fully async-first allocator? |
| 193 | +- What are the real costs of lock-free memory management? |
| 194 | +- How do we handle task isolation efficiently? |
| 195 | +- What patterns emerge in async memory usage? |
| 196 | + |
| 197 | +While the current implementation is not performance-competitive, I think it offers some insights on where to go next. |
| 198 | + |
| 199 | +## Final Disclaimer |
| 200 | + |
| 201 | +This was built as an exploration of the boundaries of async Rust. Don't use this in production unless you enjoy debugging memory allocation patterns more than having a functional application. |
| 202 | + |
| 203 | +--- |
| 204 | +*Last updated: October 26, 2024* |
0 commit comments