如何在Go中编写基准测试_Go Benchmark函数写法

Go基准测试函数必须以Benchmark为前缀、参数为*testing.B且文件名以_test.go结尾;运行命令为go test -bench=.;b.N由框架动态调整以确保总耗时约1秒,不可手动赋值。

Go基准测试必须用Benchmark前缀函数

Go的go test只识别以Benchmark开头、参数为*testin

g.B的函数。写成TestXxxbenchXxx都不会被运行。

  • 函数签名必须是func BenchmarkXxx(b *testing.B),大小写和参数类型都不能错
  • 文件名需以_test.go结尾,且与被测代码在同一包下(通常同目录)
  • 运行命令是go test -bench=.,不是go rungo build

b.N不是循环次数,而是框架动态调整的迭代基数

Go基准测试会自动多次运行函数,并根据耗时调整b.N值,目标是让单次执行时间稳定在约1秒左右。你不能手动设b.N = 1000来“控制次数”——这会被忽略,且破坏结果准确性。

  • 所有逻辑必须放在for i := 0; i 循环内,否则计时不包含实际工作
  • 初始化开销(如构建大slice、打开文件)应放在b.ResetTimer()之前,避免计入基准耗时
  • 若需预热(warm-up),可加一次空跑:for i := 0; i ,再调b.ResetTimer()
func BenchmarkMapAccess(b *testing.B) {
    m := make(map[int]int)
    for i := 0; i < 1000; i++ {
        m[i] = i * 2
    }
    b.ResetTimer() // 重置计时器,跳过建map的开销
    for i := 0; i < b.N; i++ {
        _ = m[i%1000] // 实际被测操作
    }
}

避免在基准测试中触发GC或内存分配干扰结果

频繁分配内存会引入GC抖动,导致ns/op波动大、不可比。尤其注意字符串拼接、fmt.Sprintf、切片append未预分配等隐式分配。

  • b.ReportAllocs()开启内存统计,观察B/opallocs/op
  • 提前分配好切片容量:result := make([]int, 0, b.N),而非make([]int, b.N)(后者可能浪费空间)
  • 避免在循环内调用log.Printfmt.Println——它们分配并写IO,直接让基准失效

对比多个实现时,用子基准测试保持环境一致

不要写多个独立的BenchmarkXxx函数来比性能——它们可能在不同GC周期、不同CPU调度下运行。改用b.Run做子基准,共享同一轮驱动逻辑。

  • 每个b.Run子项单独计时,但复用外层预热和初始化
  • 名字用短标识符(如"map""slice"),避免空格或特殊字符
  • 子基准里仍要遵守b.N循环规则,不能省略
func BenchmarkSearch(b *testing.B) {
    data := make([]int, 1e6)
    for i := range data {
        data[i] = i
    }
    b.Run("map", func(b *testing.B) {
        m := make(map[int]bool)
        for _, x := range data {
            m[x] = true
        }
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
            _ = m[data[i%len(data)]]
        }
    })
    b.Run("slice", func(b *testing.B) {
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
            found := false
            for _, x := range data {
                if x == data[i%len(data)] {
                    found = true
                    break
                }
            }
            _ = found
        }
    })
}

真实压测中,b.N可能从几万跳到几百万,取决于函数快慢;如果发现allocs/op远高于预期,大概率是某处悄悄new了对象——这时候go tool pprof比盯着数字更有用。