基准测试中重置状态不能靠defer,因为子基准不共享父基准的defer栈且testing.B无Cleanup机制;需用b.StopTimer()+b.StartTimer()在每次迭代前重置并排除重置开销。

基准测试中重置状态为什么不能靠 defer
因为 b.Run() 启动的子基准不共享父基准的 defer 栈,且 testing.B 不提供类似 t.Cleanup() 的机制。你在顶层 BenchmarkXxx 函数里写 defer cleanup(),只会在整个基准函数退出时执行一次,而无法保证每次循环迭代后都重置——这恰恰是回溯类、状态机类算法(如 Sudoku 求解、N-Queens)最需要的。
用 b.StopTimer() + b.StartTimer() 做每次迭代的重置
当被测函数会就地修改输入(比如 *Board 或 map[string]int),你必须在每次 i 迭代前重置数据,并确保重置时间不计入统计。标准做法是:
- 在
for i := 0; i 循环内,先调用 <code>b.StopTimer() - 执行重置操作(例如
copy(dst, src)、resetMap(m)、或重新构造结构体) - 再调用
b.StartTimer() - 然后调用被测函数
注意:不要把 b.StopTimer() 放在循环外,否则第一次迭代后的重置就没计时隔离;也不要漏掉 b.StartTimer(),否则整个循环都不计时。
数组 vs 切片:重置方式差异极大
[9][9]int 类型的 board 可以直接赋值重置:board = exampleBoard,Go 会逐元素复制,安全高效;但若字段含 []int 或 *Node,就必须深度克隆,否则多个迭代共享底层底层数组或指针,导致状态污染。
- 切片重置推荐用
dst = append(dst[:0], src...)或显式copy(dst, src) - 含指针/接口字段的结构体,别手写 clone —— 用
github.com/google/go-cmp/cmp的DeepCopy或自定义Clone()方法 - 避免在重置逻辑里调用
fmt.Sprintf、json.Marshal等高开销操作,它们会拖慢b.StopTimer()区段,虽不计时但影响整体吞吐
别混淆 b.ResetTimer() 和循环内重置
b.ResetTimer() 是一次性动作,用于丢弃从函数入口到它被调用时刻的所有计时与内存统计,适合「全局初始化后开始正式计时」场景;但它无法解决「每次迭代需独立状态」的问题。如果你只在循环外调一次 b.ResetTimer(),却没在循环内重置数据,第二次迭代实际运行在已修改过的 board 上——结果不是变慢,而是直接提前返回或 panic。
真正关键的不是计时器怎么 reset,而是每次迭代开始前,输入是否干净。计时控制只是让这个干净性可测量的配套手段。

















