Golang的性能基准对比是通过内置的testing包实现的,开发者可以编写基准测试函数,借助go test命令的相关参数完成不同代码实现的性能对比,判断优化方案的实际效果。

Golang性能基准测试基础规则
基准测试函数需要放在_test.go结尾的文件中,函数名必须以Benchmark开头,参数为*testing.B,函数内部需要通过b.N控制循环次数,框架会自动调整b.N的数值,让基准测试运行足够长的时间,得到稳定的结果。
下面是一个最基础的基准测试示例,对比两种字符串拼接方式的性能:
package main
import (
"bytes"
"strings"
"testing"
)
// 使用+拼接字符串的基准测试
func BenchmarkStringConcatWithPlus(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = "hello" + "world" + "golang"
}
}
// 使用strings.Builder拼接字符串的基准测试
func BenchmarkStringConcatWithBuilder(b *testing.B) {
for i := 0; i < b.N; i++ {
var builder strings.Builder
builder.WriteString("hello")
builder.WriteString("world")
builder.WriteString("golang")
_ = builder.String()
}
}
// 使用bytes.Buffer拼接字符串的基准测试
func BenchmarkStringConcatWithBuffer(b *testing.B) {
for i := 0; i < b.N; i++ {
var buffer bytes.Buffer
buffer.WriteString("hello")
buffer.WriteString("world")
buffer.WriteString("golang")
_ = buffer.String()
}
}
运行基准测试并解读结果
在终端中执行以下命令运行基准测试,-bench=.表示运行所有基准测试,-benchmem会额外输出内存分配相关的指标:
go test -bench=. -benchmem
运行后得到的典型结果如下:
goos: linux goarch: amd64 pkg: example/benchmark cpu: Intel(R) Core(TM) i7-10700 CPU @ 2.90GHz BenchmarkStringConcatWithPlus-8 1000000000 0.2842 ns/op 0 B/op 0 allocs/op BenchmarkStringConcatWithBuilder-8 1000000000 0.5213 ns/op 0 B/op 0 allocs/op BenchmarkStringConcatWithBuffer-8 373267412 3.213 ns/op 0 B/op 0 allocs/op PASS ok example/benchmark 2.345s
结果中各个指标的含义如下:
- 函数名-8:8表示GOMAXPROCS的数值,即参与运行的CPU核心数
- 1000000000:b.N的最终数值,即该基准测试运行的循环次数
- 0.2842 ns/op:每次操作消耗的纳秒数,数值越小性能越好
- 0 B/op:每次操作分配的内存字节数
- 0 allocs/op:每次操作的内存分配次数
性能基准对比的注意事项
控制测试变量
对比两个实现的性能时,要保证两者的逻辑除了核心实现部分之外完全一致,比如输入参数、前置准备逻辑都要相同,避免其他变量影响结果。如果基准测试中有耗时的前置准备逻辑,需要在循环外完成,或者使用b.ResetTimer()重置计时器:
func BenchmarkWithPrepare(b *testing.B) {
// 前置准备逻辑,不计入基准测试时间
data := make([]int, 10000)
for i := range data {
data[i] = i
}
// 重置计时器,只统计后续循环的时间
b.ResetTimer()
for i := 0; i < b.N; i++ {
// 核心测试逻辑
_ = sum(data)
}
}
func sum(arr []int) int {
total := 0
for _, v := range arr {
total += v
}
return total
}
避免编译器优化干扰
如果基准测试中的计算结果没有被使用,编译器可能会直接优化掉整个循环,导致结果不准确。需要把结果赋值给全局变量,或者使用testing.B的SetBytes等方法避免优化:
var globalResult int
func BenchmarkAvoidOptimize(b *testing.B) {
for i := 0; i < b.N; i++ {
// 把结果赋值给全局变量,避免被编译器优化
globalResult = i * 2
}
}
多次运行取稳定结果
单次基准测试的结果可能受系统当前负载影响,建议多次运行,或者加上-count=3参数让每个基准测试运行3次,取稳定的结果:
go test -bench=. -benchmem -count=3
常见场景的基准对比实践
对比不同数据结构的性能
比如对比map和sync.Map在并发读场景下的性能差异:
package main
import (
"sync"
"testing"
)
func BenchmarkMapRead(b *testing.B) {
m := make(map[int]int)
for i := 0; i < 1000; i++ {
m[i] = i
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = m[i%1000]
}
}
func BenchmarkSyncMapRead(b *testing.B) {
var m sync.Map
for i := 0; i < 1000; i++ {
m.Store(i, i)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = m.Load(i % 1000)
}
}
对比不同算法的性能
比如对比冒泡排序和快速排序的性能差异:
package main
import (
"math/rand"
"sort"
"testing"
"time"
)
// 生成随机切片
func generateSlice(n int) []int {
rand.Seed(time.Now().UnixNano())
arr := make([]int, n)
for i := range arr {
arr[i] = rand.Intn(n)
}
return arr
}
// 冒泡排序
func bubbleSort(arr []int) {
n := len(arr)
for i := 0; i < n-1; i++ {
for j := 0; j < n-i-1; j++ {
if arr[j] > arr[j+1] {
arr[j], arr[j+1] = arr[j+1], arr[j]
}
}
}
}
func BenchmarkBubbleSort(b *testing.B) {
for i := 0; i < b.N; i++ {
arr := generateSlice(100)
bubbleSort(arr)
}
}
func BenchmarkQuickSort(b *testing.B) {
for i := 0; i < b.N; i++ {
arr := generateSlice(100)
sort.Ints(arr)
}
}
通过以上的方法和实践,开发者可以灵活使用Golang内置的基准测试能力,完成不同实现方案的性能对比,为代码优化提供准确的参考依据。