A frame with zero allocations
The problem
A game running at 60fps has 16.6 milliseconds to produce each frame. Miss that budget and the frame is late โ the player sees a hitch. The sneakiest way to blow it is a heap allocation inside the game loop. Most of the time it's cheap, which is exactly what makes it dangerous: it hides. Then on one frame in a thousand it isn't cheap, because the allocator hit a slow path, the free list had fragmented, or the operating system had to hand back a fresh page of memory.
That's the trap with frame pacing: it's a worst-case problem, not an average one. You can allocate ten thousand times with a lovely average and still ship a visible stutter, because the player feels the one frame that ran long, not the mean. General-purpose allocators are tuned to make the average good and to be safe across every possible usage pattern โ which is the right goal for most software and the wrong guarantee for a real-time loop, where predictability matters more than raw speed.
How I'm approaching it
The move is to stop asking the general-purpose heap for per-frame memory at all. Almost everything a frame allocates is transient โ this frame's particle list, the popup queue, scratch space for pathfinding โ and all of it can die at the end of the frame. So I hand that work to an arena: a single block of memory reserved once, up front.
Allocating from an arena is trivial: you keep a marker for how far you've filled it, and each request just hands back the next slice and advances the marker. There's no bookkeeping, no search for a free block, no chance of fragmentation. And freeing is the good part โ you don't free objects one by one. At the top of the next frame you reset the marker back to the start, and the entire frame's worth of allocations is gone in a single operation. Releasing ten thousand objects costs exactly the same as releasing one.
The bottlenecks I'm watching
- Lifetime discipline. Because the arena is wiped every frame, nothing that must survive into the next frame can point into it. Getting that boundary wrong is a dangling pointer, so the rule has to be simple and enforced: arena memory is frame-local, full stop.
- Sizing the reservation. Too small and you spill back to the very heap you were trying to avoid; too large and you waste memory. That waste bites hardest on a WebAssembly build, where the linear heap is a fixed, precious budget rather than something the OS grows for you on demand.
- Non-trivial destructors. A plain reset is perfect for trivial data, but any object that owns something outside the arena still needs its cleanup to run. Keeping arena contents to simple, self-contained data avoids quietly leaking whatever they hold.
The open question
The honest hard part is that sizing number. Rather than pick it by guess, my plan is to instrument peak per-frame arena usage while the game runs through its heaviest moments, and set the reservation from the measured p99 โ big enough to swallow the worst real frame, not so big that a WASM build pays for headroom it never touches. Measure first, then commit the number.
What I expect to see
Success here doesn't look like a higher average frame rate โ it looks like a flatter one. The metric I'm after is the frame-time graph: the general-heap version should show occasional tall spikes where an allocation hit a slow path, and the arena version should erase them, leaving a much tighter band. If the p99 and p99.9 frame times collapse toward the median while the average barely moves, that's the win โ the stutter is gone even though the typical frame was never the problem. If the spikes were coming from somewhere else entirely, I'd rather find that out than assume.
What I'm looking into next
- A per-frame reset that stays cheap and safe, plus a debug guard that catches any pointer accidentally outliving its frame.
- Whether a small set of purpose-sized pools beats one big arena for the most common object types.
- How this interacts with the WASM linear heap specifically โ measuring real peak usage rather than reserving a round number and hoping.