Learn/Remade with Rust

Game Memory Allocator: Fewer Frame Spikes, Measured

A game memory allocator changes start-up cost and frame spikes. Endless Sky traces put rusty_alloc next to glibc, mimalloc, and jemalloc, with the memory bill attached.

Signed by M·
A wooden desk at night with a PC tower, a monitor showing a starfield and a small spacecraft, a keyboard, a notebook, and a lit desk lamp
What does a game memory allocator change?

A game memory allocator swap changes start-up cost and allocator frame spikes. On recorded Endless Sky traces, rusty_alloc cut spike frames versus glibc and used more peak memory unless purge was on. With page faults counted, the battle total was level.

A game memory allocator is the heap under every entity spawn, every particle, and every server tick. Game developers feel it as a hitch. We replayed the recorded allocation stream of Endless Sky, an open-source space game, through four allocators: glibc, rusty_alloc 2.2, mimalloc 2.3, and jemalloc 5. Five repetitions, rotated order, a null arm, and 95% bootstrap intervals. Start-up got cheaper. Allocator frame spikes got fewer. Peak memory got more expensive unless the purge switch is on. The battle total, once page faults are counted, sat level with glibc.

That is a measurement. rusty_alloc is the pure-Rust allocator in the set. This page is what a game memory allocator did to a real game's recorded calls, and how a studio runs the same check on a dedicated server and a client. Trust is the binary you already ship. Security is a heap that does not hide a double-free in C. Incentive is frame time you stop giving away.

Why a Game Memory Allocator Shows Up on the Frame

Players do not profile malloc. They feel a dropped frame when a battle allocates. A game memory allocator sits on that path whether the studio chose it or inherited glibc. Endless Sky's start-up was 2.4 million allocator calls on 25 threads. The first 60 seconds of a large battle were 12 million calls on 28 threads, with 816 MB live at the peak. Those recordings came from the unmodified binary. The recorder is a small LD_PRELOAD, under 200 lines of C. It logs each call and passes it straight through. The trace holds block ids, sizes, threads, and timestamps. It holds no addresses and no memory contents. What leaves the building is a report, and only if you send it.

Streaming a game is a different job. Starfire and Comet move frames after the frame exists. A game memory allocator decides whether that frame was already late. The NIST Secure Software Development Framework wants memory-safe code on untrusted sizes. An allocation size off a net packet is untrusted. The remade stack that rusty_alloc belongs to is catalogued at Remade with Rust.

What a Game Memory Allocator Replay Measured

Each claim below showed up in at least two full runs on fresh virtual machines. A one-run win is not a finding. The kit is bench/alloc-eval in the rusty_alloc repository. One command replays the stream through every allocator, checks correctness, then times it. An overnight run on an idle machine is the shape of the job.

rusty_alloc Against glibc on Allocator Frame Spikes

On start-up, rusty_alloc's allocator time landed 26% and 39% under glibc across two runs, 104 ms against 171 ms in the tighter reading. Frames where the allocator alone took more than 1 ms of a 16.7 ms frame fell 30% and 49% at start-up, and 41% and 43% in the battle: 76 frames against 133. The worst 1% of battle frames, scored as allocator cost, fell 24% and 31%. Allocator frame spikes are the row a player can feel. The combined battle cost, allocator time plus the page fault of first writing new memory, was 0.90× and 1.07×. The interval includes 1.0, so that row is level. A synthetic 20,000-frame session across level loads cut that same worst 1% by 60%, 46%, and 59% across three runs.

mimalloc and jemalloc on the Same Traces

jemalloc 5 had the cheapest start-up, 48% less allocator time than glibc, and the lowest battle memory, 160 MB under glibc. Its combined per-frame cost in the battle was 69% worse at the 99th percentile. mimalloc 2.3 faults new pages in during the allocation call, so its battle allocator time read about twice glibc's while the combined total was level. It also logged six to eleven times glibc's count of calls over 100 microseconds, on both recordings. If a live-service client already ships one of these, that build is the baseline. The kit takes another allocator in a few lines. Deputy is how a studio keeps a C allocator from walking back in through a transitive crate.

The Purge Switch Prices the Memory

By default rusty_alloc keeps freed memory for reuse. On the battle it held about 400 MB more at peak than glibc, 1,589 MB against 1,186 MB, and kept 1.6 GB after everything was freed. That is a policy, not a leak. RUSTY_ALLOC_PURGE_DELAY=10 returned memory and put battle peak 283 MB under glibc. The cost was about four times as many allocator calls over 100 microseconds, and a total about 30% higher. You pick the policy per deployment. A synthetic job-system workload won once and did not reproduce, so it is not a claim. On a synthetic 30 Hz server tick, rusty_alloc stayed between 0.5 and 1.4 seconds in all 15 runs. glibc ranged from 0.9 to 6.8 seconds. Steadier. The median gap moved too much to quote.

Run a Game Memory Allocator Check on Your Build

The published traces are Endless Sky. A live-service dedicated server, the scale a title like Star Citizen plays at, is the run this kit is for, on your hardware. The battle here is a 60-second cut, not a loaded shard. The published machine was a 24-thread laptop running Linux under WSL2, shared with other work. A replay measures the allocator's share of the work. Your own profile says how much of a frame the allocator is. Windows clients cannot be recorded yet. A converter from Windows heap tracing, ETW, is the next piece. Linux recordings replay on Windows in the meantime.

Record and Replay Before You Touch the Binary

Record the unmodified binary. Replay overnight on an idle machine. Correctness runs first: alignment, no overlapping blocks, contents preserved across frees and resizes, zeroed memory still zero. glibc runs twice under two labels. When those two disagree, that row gets no verdict. Replays follow the recording's own timeline, so threads overlap as they did in the game. A verdict is refused if a run fell more than 10% behind the trace. That check threw out one of our early results. rusty_alloc and mimalloc default to opposite memory policies, so each is also run with the other's. A policy difference is not reported as a speed difference. The Electronic Frontier Foundation keeps the file on game accounts that die with the service. A trace with no memory contents follows the same rule. The measurement does not take the player's data with it.

Allocator Frame Spikes on the Server and the Client

A dedicated server cares about allocator CPU per tick under your entity counts. A lower number with a tighter spread is headroom per instance. A client cares how many frames lose more than a millisecond to allocation, and how bad the worst 1% are. Peak memory and retained memory price any CPU gain before anyone commits. Studios also drown in design docs, patch notes, and crash writeups. RagConverter turns those files into a private, on-device index so the design history stays in the building. The Privacy Rights Clearinghouse is where leaked account piles get counted. A game memory allocator report should stay a report.

A One-Line Swap After the Numbers Agree

If the rows favour a swap, the integration test is a one-line global allocator in a build you control. The importance of Remade with Rust is why that allocator is Rust. Frames that do leave the machine still need a memory-safe codec, and rusty_h264 is that path for video. A playtest room with a camera in it should keep the picture in the house. dldeploy is the flash path for a device that does not phone a vendor. The Disco Party is the mesh those machines join. The Freedom Guide is how a person starts. Measure the game memory allocator on your server. Then decide.