Skip to main content
๐Ÿ“œ WAYPOINT LESSON

JIT effects on your measurements

โญโญโญ advancedโณ 16 min read๐Ÿ“ Lesson 219 of 265

Tiered compilation, inlining, devirtualization, dead-code elimination โ€” and how each one fakes results.

JIT effects on your measurements

The JIT is your co-author, and it edits aggressively. Four edits that fake or mask performance:

Tiered compilation (default since .NET Core 3): methods first compile fast-and-rough (tier 0), then recompile optimized after ~30 calls. A benchmark that includes tier-0 execution measures the WRONG code. Warmup is not optional; it is the difference between two compilers.

Inlining. Small methods vanish into their callers โ€” the call overhead you measured disappears. A micro-benchmark of "tiny method call overhead" often measures nothing: the call was never made.

Devirtualization + guarded devirtualization. When the JIT can see the exact type, virtual calls become direct calls (and sometimes inline). Sealed classes/interfaces with single implementations get this for free.

Dead-code elimination. Compute a result and never use it? The JIT may delete the entire computation. The classic broken micro-benchmark:

sw.Start();
for (int i = 0; i < N; i++) ExpensivePure(i);   // result discarded โ€” may be eliminated!
sw.Stop();

Fix: accumulate into a field/local and print or assert it after the loop โ€” the standard harness does this. BenchmarkDotNet's [Benchmark] handles all four automatically (its Consumer keeps results alive).

The behavioral takeaway is symmetric: the same optimizations mean real code also gets faster than source inspection suggests โ€” hot paths with small methods, sealed types, and loop-friendly data are not just style, they are JIT-friendliness measured in nanoseconds.