What do b.ResetTimer and b.StopTimer/b.StartTimer control in a Go benchmark?
answer
- the whole function is charged to ns/op
- one line above the loop
- pause and resume for work inside the loop
- pausing costs hundreds of nanoseconds each time
- a fixed cost divided by the chosen b.N
basics
~10 sb.ResetTimer zeroes the time and allocations counted so far, so fixture setup above the loop is not charged to ns/op. b.StopTimer and b.StartTimer pause and resume that same accounting around work inside the loop.
solid answer
~40 sA benchmark reports elapsed time and allocations accumulated over the whole function call, divided by `b.N` — so any fixture built before the loop is charged to the operation you meant to measure. `b.ResetTimer()` zeroes the accumulated time and the allocation counters, which is why it belongs on the line just above the `for i := 0; i < b.N; i++` loop. `b.StopTimer()` and `b.StartTimer()` pause and resume the same accounting for setup that has to happen *inside* the loop. Getting it wrong shows up as a `ns/op` that barely responds to input size, or one that falls as you raise `-benchtime`, because a one-off cost is amortised over more iterations. The trap in the fix: `b.StopTimer`/`b.StartTimer` cost real time per call, so around cheap per-iteration setup they can dominate what you are measuring.
code
go · 8 linesfunc BenchmarkEncodeChunk(b *testing.B) {
src := bytes.NewReader(corpus) // 8 MiB of pipeline records
chunk := readChunk(src) // fixture: must not be measured
b.ResetTimer()
for i := 0; i < b.N; i++ {
encodeChunk(chunk)
}
}go deeper
Remember the single line b.ResetTimer() sitting just above the b.N loop, and what it is for: keeping fixture construction out of the reported per-operation cost.
Explain the accounting precisely: the harness times the whole function call, ResetTimer zeroes the counters, StopTimer and StartTimer pause and resume them. Be able to say why setup above the loop otherwise lands in ns/op.
Show the diagnosis, not just the API: a figure that ignores input size or drifts with the benchmark duration means a fixed cost is inside the timed region. Also know that pausing per iteration can cost more than the operation you are measuring.
Own the standard your team benchmarks to: which numbers are allowed to justify a change, how fixtures are built so measurements are comparable across machines, and when a micro-benchmark is the wrong instrument for the question being asked.
## The timed region When the testing package runs a benchmark, it starts a clock and an allocation counter, calls your function, stops both, and divides by `b.N`. Everything the function does between those two points is charged to the operation — including a fixture you built at the top of the function. That is the whole problem these three methods exist to solve. ## b.ResetTimer `b.ResetTimer()` zeroes the elapsed benchmark time and the memory-allocation counters accumulated so far, and discards any custom metrics reported up to that point. It does not start or stop anything — if the clock was running it keeps running, it simply forgets what came before. The idiomatic placement is one line above the loop: ```go func BenchmarkEncodeChunk(b *testing.B) { src := bytes.NewReader(corpus) // 8 MiB of pipeline records chunk := readChunk(src) // fixture: must not be measured b.ResetTimer() for i := 0; i < b.N; i++ { encodeChunk(chunk) } } ``` Without that line, `ns/op` is `(decode the 8 MiB fixture + b.N encodes) / b.N`. For a small `b.N` the fixture dominates completely; as the harness raises `b.N` the fixture's share shrinks, which is why the reported figure moves around when you change the benchmark duration. A second reason the placement matters: the harness calls your **entire function** again for each attempt at a larger `b.N`, so expensive setup above the loop is paid on every attempt. Resetting the timer does not make that setup free in wall-clock terms — the benchmark still takes longer to run — it only keeps it out of the reported per-operation cost. ## b.StopTimer and b.StartTimer Some setup genuinely cannot be hoisted out of the loop: the operation consumes or mutates its input, so each iteration needs a fresh one. ```go func BenchmarkEncodeFresh(b *testing.B) { for i := 0; i < b.N; i++ { b.StopTimer() chunk := readChunk(bytes.NewReader(corpus)) b.StartTimer() encodeChunk(chunk) } } ``` `b.StopTimer()` pauses the clock and the allocation accounting; `b.StartTimer()` resumes both. Anything between them is excluded from `ns/op`, `B/op` and `allocs/op`. The same pair is used at the end of a benchmark for teardown you do not want measured — closing files, flushing a writer — by calling `b.StopTimer()` after the loop. ## The cost of pausing Stopping and starting is not free. Each call reads the clock and the runtime's allocation statistics, which takes on the order of hundreds of nanoseconds. If the operation under test costs 40 ns and you wrap every iteration in a stop/start pair, the measurement overhead is larger than the thing being measured, and the pause itself perturbs the CPU caches your operation was about to use. So the preference order is: 1. Hoist the fixture above the loop and call `b.ResetTimer()` once. 2. If the operation mutates its input, prepare a **slice of fixtures** before the loop and index it with `i % len(fixtures)`, or reset the input cheaply inside the timed region and accept the small constant. 3. Only reach for `b.StopTimer`/`b.StartTimer` when the per-iteration setup is genuinely expensive relative to the operation — expensive enough that leaving it in would swamp the result *and* excluding it costs proportionally little. ## Common mistakes * **Calling `b.ResetTimer()` inside the loop.** Every iteration then throws away the accumulated measurement, and only the last iteration's cost survives into `ns/op` — a wildly optimistic and unstable number. * **Forgetting `b.StartTimer()`** on some branch of the loop body, so most iterations are not timed at all. * **Assuming the timer methods change what code runs.** They change only the accounting. The setup still executes, still allocates, still takes wall-clock time; it is merely not charged to the operation. * **Believing setup above the loop is excluded automatically.** It is not. Nothing is excluded unless you exclude it. ## How the mistake shows up The honest way to catch mis-attributed setup is a benchmark that does not respond the way physics says it should: double the size of the record you encode and `ns/op` barely moves, or the figure changes noticeably between two different benchmark durations. Both are the signature of a fixed cost sitting inside the timed region and being divided by whatever `b.N` the harness happened to pick.
- What goes wrong if b.ResetTimer is called inside the loop instead of above it?Each iteration discards everything measured so far, so the reported figure reflects only the final iteration. That gives an unstable, usually far too optimistic `ns/op`, and the allocation counters are wiped the same way. Reset belongs on the line immediately before the loop, where it runs exactly once.
- Why is wrapping cheap per-iteration setup in b.StopTimer/b.StartTimer a bad trade?Each call reads the clock and the runtime's allocation statistics, costing on the order of hundreds of nanoseconds, and disturbs the CPU caches. Around a 40 ns operation the bookkeeping dominates the result. Prefer preparing fixtures before the loop, or a pre-built slice of inputs indexed by the loop variable.
- Do these methods change which code executes?No. They change only what is attributed to the operation. Setup you exclude still runs, still allocates and still lengthens the wall-clock time of the benchmark process; it simply stops being divided into `ns/op`, `B/op` and `allocs/op`. That is worth saying explicitly, because people expect the excluded work to be skipped.
saying these in an interview costs you the question
- Assumes setup before the loop is excluded automatically
- Puts b.ResetTimer inside the b.N loop
- Thinks b.StopTimer skips the setup code entirely
- Wraps every cheap iteration in a stop/start pair
- Confuses b.ResetTimer with restarting the iteration count