合法的Benchmark函数须以Benchmark开头、接收*testing.B参数、无返回值,命名符合BenchmarkXxx规范,定义在_test.go中且与被测代码同包,循环必须使用b.N而非硬编码。
Benchmark 函数Go 的基准测试函数必须以 Benchmark 开头,接收 *testing.B 参数,且不能有返回值。它不是普通函数,不能直接调用,必须由 go test -bench 触发执行。
BenchmarkXxx 模式(首字母大写),例如 BenchmarkMapAccess
_test.go 文件中定义,且和被测代码在同一个包内b.N 控制循环次数,不能硬编码次数(如 for i := 0; i )
b.ResetTimer() 前或 b.StopTimer() 区间func BenchmarkConcatString(b *testing.B) {
s1 := "hello"
s2 := "world"
b.ResetTimer() // 确保只测量拼接耗时
for i := 0; i < b.N; i++ {
_ = s1 + s2
}
}
go test -bench 的常用参数组合不加参数的 go test -bench=. 会运行所有 Benchmark 函数,但默认不显示内存分配统计,也容易因并发干扰结果。
-bench=.:运行所有 benchmark(注意是英文点号,不是星号)-bench=BenchmarkMap:精确匹配函数名前缀(支持正则,如 BenchmarkMap.*)-benchmem:启用内存分配统计(显示 B/op 和 ops/sec)-benchtime=5s:让每个 benchmark 至少运行 5 秒(而非默认的 1 秒),提升稳定性-count=3:重复运行 3 次取平均值,减少单次抖动影响-cpu=2,4,8:指定 GOMAXPROCS 值,用于测试并发敏感型代码推荐组合:go test -bench=^BenchmarkFoo$ -benchmem -benchtime=3s -count=3
基准测试结果不准,往往不是代码问题,而是测试写法本身引入了噪声或未隔离变量。
b.ReportAllocs():即使加了 -benchmem,若函数没显式声明,某些分配可能不计入统计b.N 循环里做非目标操作(如打印、文件 I/O、随机数生成),会污染耗时time.Now() 或 runtime.ReadMemStats() 手动计时:绕过 testing.B 的自动采样机制,结果不可比defer debug.SetGCPercent(-1) 临时关闭(记得恢复)
_ = result 可能被优化掉,需确保关键计算结果被实际使用(例如参与条件判断或赋值给全局变量)当你想比较 strings.Builder 和 + 拼接性能时,仅跑两个 Benchmark 不够——环境必须一致。
var input = [...]string{...} 读取)fmt.Sprintf("%d", i) 动态生成b.Run() 组织子测试,便于横向对比和复用 setup 逻辑func BenchmarkStringConcat(b *testing.B) {
data := make([]string, 100)
for i := range data {
data[i] = fmt.Sprintf("item-%d", i)
}
b.Run("plus", func(b *testing.B) {
for i := 0; i < b.N; i++ {
s := ""
for _, d := range data {
s += d
}
_ = s
}
})
b.Run("builder", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var bld strings.Builder
for _, d := range data {
bld.WriteString(d)
}
_ = bld.String()
}
})
}
真实压测中,b.N 是动态调整的,哪怕逻辑看似简单,也可能因底层指令重排、缓存命中率差异导致倍数级波动。别只看 “快了多少倍”,先确认两次运行的 ns/op 标准差是否小于 2%。