自定义 Benchmark 函数必须命名为 BenchmarkXxx,接收 *testing.B 参数,通过 go test -bench=. 运行;需在 b.N 循环外初始化并调用 b.ResetTimer(),避免测量初始化开销。

如何在 Go test 文件中定义并运行自定义 Benchmark 函数
Go 的 testing.B 是唯一合法的 benchmark 入口,所有自定义性能测量必须以 BenchmarkXxx 形式定义,且函数签名固定为 func(*testing.B)。它不是普通函数调用,不能手动传参或直接执行——必须通过 go test -bench=. 触发。
常见错误是把 benchmark 写成普通函数、漏掉 b.ResetTimer()、或在 b.N 循环外做初始化却未排除耗时,导致结果失真。
- 函数名必须以
Benchmark开头,首字母大写 - 必须接收单个
*testing.B参数 - 循环体必须用
for i := 0; i ,不可硬编码次数 - 预热或初始化逻辑应放在
for循环之前,并调用b.ResetTimer()重置计时起点
为什么必须调用 b.ResetTimer() 和 b.StopTimer()
Go 默认从函数入口开始计时,但初始化(如构造 map、读配置、建连接)不属于被测逻辑。若不干预,这些开销会污染 b.N 循环的平均耗时。
b.ResetTimer() 重置计时器,把后续 b.N 循环作为唯一测量区间;b.StopTimer() 暂停计时,适合在循环中穿插非关键操作(如日志打印、临时缓存清理),避免干扰核心路径。
立即学习“go语言免费学习笔记(深入)”;
YDUI Touch专为移动端打造,在技术实现、交互设计上兼容主流移动设备,保证代码轻、性能高;使用 Flexbox 技术,灵活自如地对齐、收缩、扩展元素,轻松搞定移动页面布局;用 rem 实现强大的屏幕适配布局,等比例适配所有屏幕;自定义Javascript组件、Less文件、Less变量,定制一份属于自己的YDUI。
- 初始化后、循环前调用
b.ResetTimer()是最常见且必要的操作 - 若循环体内需执行不可省略但非目标逻辑(如 sync.Pool Get/Pool),可用
b.StopTimer()+b.StartTimer()包裹 - 忘记
ResetTimer()会导致 benchmark 报出极低 QPS、极高 ns/op,数值完全不可比
如何测量不同输入规模下的性能变化(如 slice 长度递增)
标准 testing.B 不支持参数化 benchmark,但可通过闭包或子测试方式模拟多组输入。推荐在单个 Benchmark 函数内遍历不同规模,对每组调用 b.Run() 创建子 benchmark,便于横向对比。
注意:每个 b.Run() 子项独立计时,且会继承父项的 -benchmem 等标志,适合观察增长趋势。
func BenchmarkCopySlice(b *testing.B) {
sizes := []int{100, 1000, 10000}
for _, n := range sizes {
b.Run(fmt.Sprintf("Len%d", n), func(b *testing.B) {
data := make([]byte, n)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = append([]byte(nil), data...)
}
})
}
}
如何安全地在 benchmark 中使用并发(b.RunParallel)
b.RunParallel 用于模拟多 goroutine 并发调用同一逻辑,适用于测试锁竞争、channel 吞吐、sync.Pool 等场景。但它不保证总执行次数为 b.N,而是将 b.N 拆分给多个 goroutine 并行执行——实际调用次数 ≥ b.N,且无法控制 goroutine 数量(由 runtime 自动调度)。
关键约束:被测函数不能依赖共享可变状态,否则结果不可复现;所有初始化必须在 b.RunParallel 外完成。
- 传入的
func(*testing.PB)中,pb.Next()控制是否继续,必须用for pb.Next() { ... }循环包裹被测逻辑 - 不要在
pb.Next()循环内做初始化(如 new struct、open file),这会放大资源开销 - 并发 benchmark 的内存分配统计(
-benchmem)反映的是单次调用均值,不是总和
func BenchmarkConcurrentMapSet(b *testing.B) {
m := make(map[int]int)
var mu sync.RWMutex
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
mu.Lock()
m[1] = 1
mu.Unlock()
}
})
}
Go benchmark 的真实难点不在写法,而在于区分“测什么”和“怎么排除干扰”。比如 time.Now() 调用本身有开销,fmt.Sprintf 在循环里会掩盖核心逻辑性能,甚至 GC 周期都可能让结果抖动——这些都需要靠多次运行、关闭 GC(debug.SetGCPercent(-1))、或结合 pprof 进一步定位。










