3.2. 基准测试 (Benchmark)
3.2.1. 基准测试基础
Go 内置了基准测试框架,用于测量代码性能。
3.2.1.1. 基本语法
// benchmark_test.go
package mypackage
import "testing"
func BenchmarkFunction(b *testing.B) {
for i := 0; i < b.N; i++ {
// 被测试的代码
MyFunction()
}
}
3.2.1.2. 运行基准测试
# 运行所有基准测试
go test -bench=.
# 运行特定基准测试
go test -bench=BenchmarkFunction
# 运行并显示内存分配
go test -bench=. -benchmem
# 指定运行时间
go test -bench=. -benchtime=5s
# 运行多次取平均
go test -bench=. -count=5
3.2.2. 理解测试结果
BenchmarkFunction-8 1000000 1234 ns/op 256 B/op 4 allocs/op
字段 |
含义 |
|---|---|
|
测试名称-GOMAXPROCS |
|
运行次数 |
|
每次操作耗时 |
|
每次操作分配的字节数 |
|
每次操作的内存分配次数 |
3.2.3. 高级基准测试技巧
3.2.3.1. 1. 子基准测试
func BenchmarkConcat(b *testing.B) {
sizes := []int{10, 100, 1000}
for _, size := range sizes {
b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) {
for i := 0; i < b.N; i++ {
concatStrings(size)
}
})
}
}
3.2.3.2. 2. 重置计时器
func BenchmarkExpensiveSetup(b *testing.B) {
// 昂贵的初始化
data := loadLargeDataSet()
b.ResetTimer() // 重置计时器,不计算初始化时间
for i := 0; i < b.N; i++ {
processData(data)
}
}
3.2.3.3. 3. 暂停/恢复计时
func BenchmarkWithCleanup(b *testing.B) {
for i := 0; i < b.N; i++ {
data := generateData()
b.StartTimer()
processData(data)
b.StopTimer()
cleanupData(data)
}
}
3.2.3.4. 4. 并行基准测试
func BenchmarkParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
// 并行执行的代码
myFunction()
}
})
}
3.2.3.5. 5. 报告自定义指标
func BenchmarkThroughput(b *testing.B) {
totalBytes := int64(0)
for i := 0; i < b.N; i++ {
n := processData(data)
totalBytes += int64(n)
}
b.SetBytes(totalBytes / int64(b.N)) // 报告吞吐量
}
3.2.4. 实际案例
3.2.4.1. 案例 1:字符串拼接对比
func BenchmarkStringConcat(b *testing.B) {
strs := []string{"Hello", " ", "World", "!"}
b.Run("Plus", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var s string
for _, str := range strs {
s += str
}
}
})
b.Run("StringBuilder", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var sb strings.Builder
for _, str := range strs {
sb.WriteString(str)
}
_ = sb.String()
}
})
b.Run("Join", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = strings.Join(strs, "")
}
})
}
3.2.4.2. 案例 2:Map vs Slice 查找
func BenchmarkLookup(b *testing.B) {
sizes := []int{10, 100, 1000, 10000}
for _, size := range sizes {
// 准备数据
slice := make([]int, size)
m := make(map[int]bool, size)
for i := 0; i < size; i++ {
slice[i] = i
m[i] = true
}
target := size / 2
b.Run(fmt.Sprintf("Slice-%d", size), func(b *testing.B) {
for i := 0; i < b.N; i++ {
for _, v := range slice {
if v == target {
break
}
}
}
})
b.Run(fmt.Sprintf("Map-%d", size), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = m[target]
}
})
}
}
3.2.4.3. 案例 3:sync.Pool vs 直接分配
var bufferPool = sync.Pool{
New: func() interface{} {
return make([]byte, 1024)
},
}
func BenchmarkAllocation(b *testing.B) {
b.Run("DirectAlloc", func(b *testing.B) {
for i := 0; i < b.N; i++ {
buf := make([]byte, 1024)
_ = buf
}
})
b.Run("SyncPool", func(b *testing.B) {
for i := 0; i < b.N; i++ {
buf := bufferPool.Get().([]byte)
bufferPool.Put(buf)
}
})
}
3.2.5. 使用 benchstat 比较结果
3.2.5.1. 安装
go install golang.org/x/perf/cmd/benchstat@latest
3.2.5.2. 使用
# 运行旧版本并保存结果
go test -bench=. -count=10 > old.txt
# 修改代码后运行新版本
go test -bench=. -count=10 > new.txt
# 比较结果
benchstat old.txt new.txt
3.2.5.3. 输出示例
name old time/op new time/op delta
Function-8 1.23µs ± 2% 0.98µs ± 1% -20.33% (p=0.001 n=10+10)
name old alloc/op new alloc/op delta
Function-8 256B ± 0% 128B ± 0% -50.00% (p=0.001 n=10+10)
3.2.6. 常见陷阱
3.2.6.1. 陷阱 1:编译器优化导致代码被消除
// ❌ 结果可能被优化掉
func BenchmarkBad(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = compute() // 编译器可能消除这行
}
}
// ✅ 使用全局变量防止优化
var result int
func BenchmarkGood(b *testing.B) {
var r int
for i := 0; i < b.N; i++ {
r = compute()
}
result = r // 防止编译器优化
}
3.2.6.2. 陷阱 2:未预热缓存
func BenchmarkWithWarmup(b *testing.B) {
// 预热
for i := 0; i < 100; i++ {
myFunction()
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
myFunction()
}
}
3.2.6.3. 陷阱 3:测试数据不具代表性
// ❌ 只测试最好情况
func BenchmarkBad(b *testing.B) {
data := []int{1, 2, 3} // 数据太小
for i := 0; i < b.N; i++ {
sort(data)
}
}
// ✅ 测试多种情况
func BenchmarkGood(b *testing.B) {
for _, size := range []int{10, 100, 1000, 10000} {
b.Run(fmt.Sprintf("n=%d", size), func(b *testing.B) {
data := generateRandomData(size)
b.ResetTimer()
for i := 0; i < b.N; i++ {
dataCopy := make([]int, len(data))
copy(dataCopy, data)
sort(dataCopy)
}
})
}
}
3.2.7. 最佳实践
使用
-benchmem:总是关注内存分配多次运行:使用
-count=10确保结果稳定使用 benchstat:科学比较优化前后的差异
测试真实数据:使用生产环境的数据分布
避免编译器优化:使用全局变量保存结果
隔离测试环境:关闭其他程序,固定 CPU 频率