Go 语言完全指南 / 22 - 基准测试:benchmem、pprof、trace、性能分析
22 - 基准测试
22.1 基本基准测试
package bench
import (
"strings"
"testing"
)
func BenchmarkConcat(b *testing.B) {
for i := 0; i < b.N; i++ {
s := ""
for j := 0; j < 100; j++ {
s += "a"
}
}
}
func BenchmarkBuilder(b *testing.B) {
for i := 0; i < b.N; i++ {
var sb strings.Builder
for j := 0; j < 100; j++ {
sb.WriteString("a")
}
_ = sb.String()
}
}
# 运行基准测试
go test -bench=. -benchmem
# 输出示例:
# BenchmarkConcat-8 100000 15000 ns/op 5120 B/op 99 allocs/op
# BenchmarkBuilder-8 1000000 1200 ns/op 512 B/op 8 allocs/op
| 指标 | 说明 |
|---|
ns/op | 每次操作耗时(纳秒) |
B/op | 每次操作分配的字节数 |
allocs/op | 每次操作的内存分配次数 |
22.2 基准测试技巧
// 预热(避免编译优化影响)
func BenchmarkFoo(b *testing.B) {
result := 0
b.ResetTimer() // 重置计时器(排除初始化时间)
for i := 0; i < b.N; i++ {
result = expensiveOperation()
}
_ = result // 防止编译器优化掉
}
// 并发基准测试
func BenchmarkFooParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
expensiveOperation()
}
})
}
// 子基准测试
func BenchmarkMap(b *testing.B) {
sizes := []int{100, 1000, 10000}
for _, size := range sizes {
b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) {
for i := 0; i < b.N; i++ {
m := make(map[int]int, size)
for j := 0; j < size; j++ {
m[j] = j
}
}
})
}
}
22.3 内存分析
# 内存分配分析
go test -bench=. -benchmem -memprofile=mem.out
go tool pprof mem.out
# 交互模式
(pprof) top
(pprof) top -cum
(pprof) list BenchmarkConcat
(pprof) web # 生成图形(需要安装 graphviz)
22.4 CPU 分析
# CPU 分析
go test -bench=BenchmarkConcat -cpuprofile=cpu.out
go tool pprof cpu.out
# HTTP 服务器实时分析
import _ "net/http/pprof"
func main() {
go http.ListenAndServe("localhost:6060", nil)
// 应用代码...
}
# 访问 http://localhost:6060/debug/pprof/
22.5 trace 工具
# 生成 trace
go test -bench=. -trace=trace.out
go tool trace trace.out
# HTTP 服务器 trace
curl -o trace.out http://localhost:6060/debug/pprof/trace?seconds=5
go tool trace trace.out
22.6 性能优化技巧
// 1. 预分配
func BenchmarkPrealloc(b *testing.B) {
for i := 0; i < b.N; i++ {
s := make([]int, 0, 1000)
for j := 0; j < 1000; j++ {
s = append(s, j)
}
}
}
// 2. 避免不必要的分配
func BenchmarkStringConv(b *testing.B) {
data := []byte("hello world")
b.Run("string", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = string(data)
}
})
b.Run("unsafe", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = unsafe.String(unsafe.SliceData(data), len(data))
}
})
}
// 3. sync.Pool 减少分配
var pool = sync.Pool{
New: func() any { return new(bytes.Buffer) },
}
func BenchmarkWithPool(b *testing.B) {
for i := 0; i < b.N; i++ {
buf := pool.Get().(*bytes.Buffer)
buf.Reset()
buf.WriteString("hello")
pool.Put(buf)
}
}
🏢 业务场景
- 性能回归检测:CI 中运行基准测试对比性能
- 优化决策:比较不同实现方案的性能
- 内存泄漏排查:pprof 分析内存增长
- CPU 热点定位:pprof 找到最耗时的函数
📖 扩展阅读