What You'll Learn
- Arena allocators for bulk allocation
- Pool allocators for fixed-size objects
- Stack allocators for LIFO patterns
- When to use each allocator type
Memory Pools, Arenas & Custom Allocators
malloc and new are general-purpose allocators — they handle any size, any order. But generality has a cost: lock contention, fragmentation, and system calls. Custom allocators eliminate these costs for specific patterns, delivering 10-100× faster allocation in game engines, servers, and real-time systems.
Arena Allocator — Bump and Reset
An arena allocates by bumping a pointer forward — O(1), no fragmentation, no bookkeeping. When you're done with all objects, call reset() to "free" everything in O(1). Perfect for per-frame game data, request-scoped server data, or compiler AST nodes.
Pro Tip: Arenas are the simplest high-performance allocator. If you can group allocations into "phases" (per-frame, per-request), arenas are almost always the right choice.
Arena Allocator
Bump-pointer allocation with instant reset
#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;
// Arena allocator — bump pointer, free everything at once
class Arena {
char* buffer;
size_t capacity;
size_t offset;
public:
Arena(size_t size) : buffer((char*)malloc(size)), capacity(size), offset(0) {
cout << "Arena created: " << size << " bytes" << endl;
}
~Arena() {
free(buffer);
cout << "Arena freed (all at once)" << endl;
}
void* allocate(size_t size, s
...Pool Allocator — Fixed-Size Free List
A pool allocator pre-allocates blocks of fixed-size slots. Allocation pops from a free list — O(1). Deallocation pushes back — O(1). No fragmentation because every slot is the same size. Ideal for particles, entities, network packets, or any type created and destroyed frequently.
Common Mistake: Using a pool for variable-size objects. Pools work only when every object is the same size. For mixed sizes, use an arena or size-class allocator.
Pool Allocator
Free-list pool vs new/delete performance comparison
#include <iostream>
#include <vector>
#include <chrono>
using namespace std;
class Timer {
string label;
chrono::high_resolution_clock::time_point start;
public:
Timer(const string& l) : label(l), start(chrono::high_resolution_clock::now()) {}
~Timer() {
auto us = chrono::duration_cast<chrono::microseconds>(
chrono::high_resolution_clock::now() - start).count();
cout << label << ": " << us << " µs" << endl;
}
};
// Fixed-size pool allocator with
...Stack Allocator — LIFO Order
A stack allocator works like the call stack: allocations grow forward, deallocations must happen in reverse order. Faster than an arena when you need per-scope cleanup (function temporaries, scoped scratch buffers).
Stack Allocator
LIFO allocation for scoped temporaries
#include <iostream>
#include <cstdlib>
using namespace std;
// Stack allocator — LIFO, fast, no fragmentation
class StackAllocator {
char* buffer;
size_t capacity;
size_t offset;
struct Header { size_t size; };
public:
StackAllocator(size_t size)
: buffer((char*)malloc(size)), capacity(size), offset(0) {}
~StackAllocator() { free(buffer); }
void* push(size_t size, size_t align = 8) {
size_t aligned = (offset + align - 1) & ~(align - 1);
size
...Quick Reference
| Allocator | Alloc | Free | Best For |
|---|---|---|---|
| Arena | O(1) bump | O(1) reset all | Per-frame/per-request data |
| Pool | O(1) pop | O(1) push | Fixed-size objects |
| Stack | O(1) bump | O(1) LIFO | Scoped temporaries |
| malloc/new | O(1)~O(n) | O(1)~O(n) | General purpose |
Lesson Complete!
You can now build arena, pool, and stack allocators for high-performance C++ applications.
Sign up for free to track which lessons you've completed and get learning reminders.