Quant Memo
Core

Microbenchmarking Pitfalls

Why timing a small piece of code in isolation is much easier to get wrong than it looks, and the specific ways a microbenchmark can report a number that has almost nothing to do with real production performance.

Prerequisites: Latency vs Throughput, The CPU Cache Hierarchy

A developer swaps a hash map for a sorted array in a hot path, writes a ten-line timing loop, and it reports the new version is 3x faster. They ship it. In production the change makes no measurable difference — or worse, it's slower. This happens constantly, because a microbenchmark — a small, isolated timing of one function or snippet — is measuring a laboratory condition that production code never experiences: a warm cache, a predictable input pattern, a compiler that's free to notice the benchmark's output is never used and delete the whole computation.

The core problem is that a microbenchmark is too clean. Real production code runs with a cold or partially-evicted cache (competing with everything else running on the box), branch predictors trained on production's actual, messier data distribution, and inputs that vary far more than a benchmark's fixed test array. A ten-line loop that calls the same function ten million times in a row gives the CPU's cache and predictor a perfect, repetitive pattern to learn — a pattern the real call site, invoked once per market tick with different data each time, never gets to exploit. The benchmark can report a number 10x better than what production ever sees, and both numbers can be "correct" measurements of two genuinely different situations.

A second failure mode is the compiler. Modern optimizing compilers perform dead code elimination: if a computed result is never used, the compiler is entitled to delete the code that computes it, because doing so changes nothing observable. A naive benchmark that computes result = expensive_function(x) and never reads result can have the entire call optimized away, timing an empty loop and reporting a blazing, meaningless number.

microbenchmark 8 ns production 65 ns
A tight loop over the same hot cache lines can report a latency several times lower than the same function measured in its real call site, where the cache is cold and inputs vary.

Worked example: the compiler deletes your benchmark

A developer benchmarks a checksum function: for i in 1..10_000_000 { checksum(buf) }, timing the loop, without ever printing or storing the returned checksum. Because the result is provably unused, an optimizing compiler can eliminate the call to checksum entirely (or, worse, hoist it out and compute it once, since buf doesn't change across iterations) — the loop reports 0.3 ns per call, implying the function is essentially free. The fix: accumulate the results into a variable the benchmark reads and prints at the end (or use a benchmarking library's explicit "black box" hint), which forces the compiler to actually keep the computation. Re-measured this way, the same function reports 40 ns per call — over 100x higher, and much closer to what production sees.

What this means in practice

Trustworthy microbenchmarks vary their inputs run to run (to defeat branch predictor and cache overfitting), force use of every computed result (to defeat dead code elimination), warm up before timing (to separate JIT/cache warm-up cost from steady-state cost), and are cross-checked against a coarser end-to-end measurement in something closer to production conditions. A microbenchmark is a hypothesis-generation tool, not a final verdict — treat any large claimed speedup with suspicion until it's confirmed in a realistic environment.

A microbenchmark measures code in an artificially clean environment — warm caches, repetitive inputs, a compiler free to delete unused work — so its numbers can diverge wildly, in either direction, from the same code's behavior in production.

The most common silent failure is dead code elimination: a benchmark that never reads its computed result can have the compiler delete the very work being timed, reporting an impossibly fast number for code that, when actually used, does real work. Always force consumption of the result before trusting a microbenchmark's timing.

Related concepts

Practice in interviews

Further reading

  • Gregg, Systems Performance, ch. 12 (Benchmarking)
ShareTwitterLinkedIn