Charlie KrugThe Build Log

← All posts

To time your own RAM, you have to outrun the prefetcher

Latency Ladder runs live micro-benchmarks of your device's cache, RAM, IndexedDB, and network with a pointer-chasing WASM kernel that beats the prefetcher, then draws all four on one log scale.

Write a loop that reads a big array as fast as it can, time it, and you will not learn how slow your memory is. You'll learn how good your CPU is at hiding it. The prefetcher watches your access pattern, sees you marching in a straight line, and quietly fetches the next chunk before you ask for it. By the time your code reaches an address, the data is already sitting in cache. The number you measured is throughput, and it has almost nothing to do with the latency of a single trip out to RAM.

That gap is the whole reason Latency Ladder exists. You've seen the chart that every programmer is supposed to know: L1 about a nanosecond, RAM about a hundred, network about a hundred milliseconds. Those numbers were measured once, years ago, on a machine you have never touched, and they're presented as four disconnected facts instead of one story, so the size of the gaps never actually lands. Click measure me and the page benchmarks the real thing on the device you're reading this on, right now.

Chase a pointer, not a sequence

To measure latency instead of throughput, you have to make the CPU wait on each access with nowhere to run ahead. The standard trick is a pointer chase: build a linked list where every node's address is scrambled, then walk it so that reading node N gives you the location of node N+1. The processor can't prefetch node N+1 because it doesn't know where N+1 is until N comes back. Every hop is a full round-trip to memory, serialized, no hiding.

The clever part is that the same kernel measures both the cache tier and the RAM tier. The only thing that changes is the size of the linked list. Size it to fit inside L1 and every hop is a cache hit, so you're timing your cache. Size it well past any consumer last-level cache and every hop misses all the way out to main memory. Identical code path, one variable different, which is what makes the two numbers genuinely comparable rather than two different benchmarks you're eyeballing side by side.

There's a second problem hiding under the first one: JavaScript itself. A hot loop in JS is at the mercy of the JIT warming up, deoptimizing, and pausing for garbage collection mid-measurement, and any of that noise swamps a nanosecond-scale signal. So the pointer-chase kernel isn't JS. It's written in AssemblyScript and compiled to WebAssembly, which gives the timing loop a stable, predictable machine-code path outside the JS engine's variance. AssemblyScript, specifically, because it's a pure npm dependency: no Rust toolchain to install in CI, but you still get your hot loop out of the JIT's reach.

Nine orders of magnitude, on one scale

Cache runs in single-digit nanoseconds. A network round-trip runs in milliseconds. That's a span of nine-plus orders of magnitude between the fastest and slowest bar, and on a linear axis it's useless: cache and RAM collapse into invisible slivers pinned against zero while network eats the entire chart. So the bars render on a shared logarithmic scale, which is the only way the relative gap becomes something you can feel instead of just read. The four bars animate in and settle in ascending order, and then the page writes one sentence from the run's own numbers: your network round-trip is slower than 40,000 of your own RAM accesses. That line, generated live from your measurements, is the entire point of the page.

Each tier runs many trials and reduces them with a trimmed mean: throw out the top and bottom 20%, average the rest. It's the cheapest defense against a single GC pause or a background tab stealing the CPU mid-trial, and it's enough to get a number that holds its order of magnitude across repeated clicks. I'll be honest about scope: this is a five-minute "huh, cool" toy, not an instrument for tuning production systems. The network tier measures a same-origin fetch, because that's what a static site with no backend can honestly measure, and I'd rather measure a real round-trip than invent a server to look more impressive.

Try it

Open Latency Ladder and hit measure me. Watch the cache and RAM bars land first, then IndexedDB, then network sliding far off to the right, and read the sentence it writes about your own hardware. Run it again and the last run leaves a faint reference marker so you can see the jitter. Then copy the result card and go argue with the famous chart using numbers from a machine you've actually touched. The kernel, the runners, and the trimmed-mean reducer are all on GitHub.

Latency Ladder is live. Free, in your browser, no signup.

This post is part of the build log: every app my automated factory ships gets written up here, honestly. Browse everything at apps.charliekrug.com. Comments are open below.

Comments

Loading comments…