Go基准测试需严格遵循规则:文件名以_test.go结尾,函数名以Benchmark开头且首字母大写,签名必须为func(b *testing.B);否则go test -bench=.无法识别执行。
go 的 go test 命令原生支持基准测试,但直接跑 go test -bench=. 很容易测出假象——比如把初始化开销、gc 抖动、编译器优化干扰全算进结果里。真正能指导优化的基准测试,必须手动控制计时边界、隔离干扰、并理解 b.n 的动态含义。
基准测试函数命名与文件位置必须严格匹配
Go 不会自动发现或运行任意函数,它只认符合硬性规则的入口:
- 文件名必须以
_test.go结尾(如http_client_test.go) - 函数名必须以
Benchmark开头,且首字母大写(如BenchmarkHTTPGet) - 函数签名只能是
func(b *testing.B),不能多参数、不能有返回值 - 测试代码必须放在
for i := 0; i 循环体内,不能漏掉这层包裹
b.ResetTimer() 不是可选操作,而是必做步骤
很多初学者在循环前做初始化(比如构建 HTTP client、预分配切片、解析 JSON 模板),却没调用 b.ResetTimer(),导致这些一次性开销被平摊进每 op 耗时,结果虚高且不可比。
正确做法是:
- 所有前置 setup 放在
for循环之前 - 紧接着调用
b.ResetTimer()—— 它会清零已记录的时间和内存分配计数 - 再进入
for循环执行被测逻辑
反例:b.ResetTimer() 放在循环中间或之后,完全失效;更糟的是根本没写这行。
测响应时间不能只靠 b.N,得自己控并发+采延迟分布
go test -bench=. 默认输出的 ns/op 是吞吐量指标(单位时间完成多少次),不是单次请求的 P95 延迟。如果你要压测一个 HTTP handler,b.N 会被框架自动调整,实际并发数不可控,且无法拿到延迟分位数。
真实场景应这样写:
- 用
sync.WaitGroup+ 固定 goroutine 数(比如 10 个)模拟并发客户端 - 每个 goroutine 发固定次数请求(如 100 次),把每次
time.Since(start)记进切片 - 调用
sort.Slice排序后手动取durations[len(durations)*95/100]算 P95 - 用
b.ReportMetric(value, "p95-sec")上报,单位必须是秒(.Seconds())
漏掉 b.ResetTimer() 或直接用 b.RunParallel,都会让延迟数据失真。
运行命令里的隐藏开关决定结果可信度
go test -bench=. 默认只跑 1 秒,b.N 可能太小导致统计噪声大;不加 -benchmem 就看不到内存分配是否成为瓶颈;不加 -benchtime=5s 很难判断结果是否稳定。
推荐组合:
go test -bench=BenchmarkFoo -benchmem -benchtime=5s -run=none-
-run=none确保跳过所有TestXXX,避免单元测试干扰基准耗时 - 若怀疑 GC 干扰,可临时加
debug.SetGCPercent(-1)关闭 GC(仅限纯计算型基准)
最易被忽略的一点:不同机器上跑出的 BenchmarkXxx-8 末尾数字代表 GOMAXPROCS,若未固定 GOMAXPROCS=1,多核调度抖动会让结果不可复现。


















