Allocator Guide¶
Understanding the Allocator Architecture¶
PrototypeSTL's allocator model has three layers. Understanding this hierarchy is key to effective memory management.
Layer 1: Memory Resources¶
Memory resources own raw bytes. They don't know about types or objects.
// Stack-allocated arena (most common)
prototype::memory_arena<4096> arena;
// Static arena (program lifetime)
static prototype::memory_arena<65536> global_arena;
Layer 2: Typed Allocators¶
Allocators adapt a memory resource for typed allocation.
prototype::arena_allocator<int> alloc(arena);
int* p = alloc.allocate(10); // 10 ints from the arena
alloc.deallocate(p, 10); // no-op for arena_allocator
Layer 3: Containers¶
Containers use allocators transparently.
prototype::vector<int, prototype::arena_allocator<int>> v(alloc);
v.push_back(42); // allocation goes through the allocator → arena
Allocator Selection Guide¶
arena_allocator Bulk Allocation, Bulk Free¶
Pattern: Allocate many objects, free them all at once.
prototype::memory_arena<8192> arena;
void process_request(Request& req) {
prototype::arena_allocator<char> alloc(arena);
// All allocations during request handling
prototype::vector<int, prototype::arena_allocator<int>> temp(alloc);
prototype::string<prototype::arena_allocator<char>> response(alloc);
// ... process ...
arena.reset(); // O(1) frees everything
}
Best for: Game frames, HTTP requests, compiler phases, per-frame rendering.
pool_allocator Uniform-Size Objects¶
Pattern: Many objects of the same size, allocated/freed individually.
// Pool of 1000 Node objects
prototype::pool_allocator<Node> pool(1000);
void event_loop() {
Node* n = pool.allocate(1); // O(1)
// ... use node ...
pool.deallocate(n, 1); // O(1)
}
Best for: List nodes, tree nodes, event objects, connection objects.
stack_allocator LIFO Temporaries¶
Pattern: Nested allocations freed in reverse order.
prototype::memory_arena<2048> scratch;
prototype::stack_allocator<float> alloc(scratch);
void compute() {
auto mark = alloc.get_marker();
float* buf1 = alloc.allocate(100);
float* buf2 = alloc.allocate(200);
// ... compute using buf1, buf2 ...
alloc.free_to_marker(mark); // Frees buf2 and buf1
}
Best for: Recursive algorithms, temporary buffers, scratch space.
Sizing Arenas¶
Formula¶
Rules of Thumb¶
| Use Case | Recommended Size |
|---|---|
| Small request handler | 4 KB |
| Game frame allocations | 64 KB - 1 MB |
| Compiler phase | 1 MB - 16 MB |
| Embedded sensor buffer | 256 B - 4 KB |
Composing Allocators¶
Multiple allocators can share a single arena:
prototype::memory_arena<16384> arena;
// Different typed allocators, same backing memory
prototype::arena_allocator<int> int_alloc(arena);
prototype::arena_allocator<float> float_alloc(arena);
prototype::arena_allocator<Node> node_alloc(arena);
prototype::vector<int, prototype::arena_allocator<int>> ints(int_alloc);
prototype::vector<float, prototype::arena_allocator<float>> floats(float_alloc);
prototype::list<Node, prototype::arena_allocator<Node>> nodes(node_alloc);
// All share the 16KB arena
Debug Tracking¶
In debug mode (PROTOTYPE_DEBUG=1), allocators track:
- High water mark (peak usage)
- Allocation count
- Deallocation count
- Leak detection (allocations without matching deallocations)
#ifdef PROTOTYPE_DEBUG
printf("Arena used: %zu / %zu bytes\n", arena.used(), arena.capacity());
#endif
Common Mistakes¶
Dangling allocator
Arena too small
Size your arenas for worst-case usage. An undersized arena returns nullptr, causing crashes or assertions.
Measure, then size
Use debug-mode tracking to measure actual peak usage, then set arena size to 2× peak for safety margin.