Skip to main content
๐Ÿ“œ WAYPOINT LESSON

Measuring allocations honestly

โญโญโญ advancedโณ 14 min read๐Ÿ“ Lesson 165 of 265

GC.GetAllocatedBytesForCurrentThread, JIT warmup, and why a single measurement lies.

Measuring allocations honestly

The probe that answers "did this allocate?":

long before = GC.GetAllocatedBytesForCurrentThread();
Work();
long delta = GC.GetAllocatedBytesForCurrentThread() - before;

Per-thread is the point: background GC activity, other threads' noise, all excluded. But two honest-measurement rules survive any probe:

  1. Warm up first. First calls pay JIT compilation, generic dictionaries populate, delegates allocate. Measure call #2 onward, or loop N times and divide โ€” a single cold measurement lies by orders of magnitude.
  2. Delta is the truth, not the absolute. Absolute bytes include the harness. The number that matters is the DIFFERENCE between two code paths doing the same work.

Reading results like a performance engineer:

  • Exactly 0 โ€” the fast path is allocation-free. Verify with a loop of 100 iterations: a hidden 24-byte allocation per call shows up as 2400.
  • Small constant โ€” one fixed allocation; find and question it (a tuple? a boxed value? a delegate cache miss?).
  • Linear in N โ€” the loop allocates per iteration. Span/for-loop instead of LINQ is the usual fix.

What this probe CANNOT tell you: total memory pressure (other threads), retained size (leaks โ€” that is GC.GetTotalMemory over time), or latency (that is a benchmark, and a dishonest one unless you follow Module 15's rules). Allocation measurement is one instrument of several; the habit of REACHING FOR IT before arguing is what distinguishes engineers who ship performance from engineers who ship vibes.