基准测试函数必须以benchmark开头、接收testing.b参数、置于_test.go文件中,否则不执行且无报错;如benchmarkadd(b testing.b)正确,benchmarkfoo或传*testing.t则被忽略。

怎么写一个能跑起来的 Benchmark 函数
Go 的基准测试不是靠 go test 默认执行的,必须显式启用,且函数名、签名有硬性要求。写错就压根不运行,还不会报错——这是最常踩的坑。
必须满足:函数名以 Benchmark 开头,接收 *testing.B 参数,放在 _test.go 文件里(和单元测试同位置)。
-
func BenchmarkFoo(b *testing.B)✅ 正确 -
func benchmarkFoo(b *testing.B)❌ 小写开头,被忽略 -
func BenchmarkFoo() bool❌ 签名错误,编译不报错但不识别 -
func BenchmarkFoo(t *testing.T)❌ 传了*testing.T,会被当成普通测试跳过
示例:
func BenchmarkAdd(b *testing.B) {
for i := 0; i <h3>
<code>go test -bench</code> 不输出结果?常见原因和绕过方法</h3><p>默认 <code>go test -bench=.</code> 什么也不显示,不是代码错了,而是 Go 要求基准测试至少跑够 1 秒(或达到最小迭代次数),否则直接跳过——尤其对极快的操作(比如几个纳秒的整数加法),<code>b.N</code> 可能被设为 1 后立即结束,不打印任何数据。</p>
- 加
-benchmem看内存分配,有时能触发输出 - 用
-benchtime=5s延长最低运行时间,强制多跑几轮 - 加
-count=3多跑几次取平均,顺便看稳定性 - 如果函数太快,手动在循环里重复逻辑(比如
for j := 0; j ),再把 <code>b.N当外层循环
错误现象:go test -bench=. -benchmem 返回空行,没报错也没数字——大概率是单次执行太短,被跳过了。
b.ResetTimer() 和 b.StopTimer() 什么时候必须用
基准测试只该测量目标代码,但初始化、预热、构造输入这些操作也会被计入耗时。不隔离它们,测出来的是“准备+执行”,不是真实性能。
-
b.StopTimer():暂停计时器,适合做一次性 setup(如生成大 slice、打开文件) -
b.StartTimer():恢复计时,通常紧跟在StopTimer()后面 -
b.ResetTimer():重置已记录的时间和内存统计,适合在 warmup 后清掉预热开销(比如第一次 map 查找可能触发扩容)
典型场景:测试 map 查找前要先建好 map,否则建 map 的时间全算进 benchmark 里。
func BenchmarkMapGet(b *testing.B) {
m := make(map[int]int)
for i := 0; i <h3>为什么 <code>Benchmark</code> 不能用 <code>fmt.Println</code> 或全局变量</h3><p>基准测试会多次调用你的函数(<code>b.N</code> 次),而且 Go 会自动调整 <code>b.N</code> 让总耗时接近目标时间。如果函数里有副作用(比如改全局变量、打印到 stdout、写文件),结果不可复现,甚至 panic。</p>
- 所有输出(
fmt.Println,log.Print)都会极大拖慢速度,干扰计时,必须删掉 - 避免读写包级变量,不同 benchmark 间可能互相污染(比如两个 benchmark 共享同一个
sync.Pool) - 如果真要调试,用
b.Log()或b.Logf(),它只在加-benchmem -v时输出,不影响计时
容易被忽略的一点:即使你没显式用全局变量,闭包捕获的外部变量(比如在 benchmark 函数外定义的 var data = make([]byte, 1024))也等价于全局状态——应该在每次循环内重新创建,或用 b.StopTimer() 隔离初始化。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











