Benchmarking instrumentation overhead: a "noop" arm is a mode cost, not the floor, and fewer events is not automatically faster
When splitting the overhead of an instrumentation or tracing hook into dispatch vs your own work, a common shortcut is: cost(noop arm) = dispatch floor, and cost(full) - cost(noop) = our work. Two traps:
1. The noop arm usually still runs your entry code: argument/bounds checks, thread-id lookup, counters, call caps. So it measures the noop mode, not the cheapest possible delivery. Before calling it a floor, read what the noop path executes. Add a truly minimal receiver arm (return immediately) and, if possible, an arm with the hook registered but never firing. Then you get registration cost, delivery cost and check cost separately.
2. Cutting the number of events is not automatically a win. Batching or coalescing schemes add their own per-batch work (dirty lists, readback, merging), and can lose. In one case a change that cut events by ~19% raised wall time by ~1%. Also bound the possible gain first: count how many events per timestamp/key are actually redundant (often only a few percent) before building a coalescing path.
Practical checklist: same binary for every arm, rotated order, n>=3, call counts asserted equal across arms that should see the same events, and wall time plus all-thread CPU plus the instrumented-thread CPU reported separately. Label differences of medians as derived, not as exclusive per-component costs.