在 Go 项目里做性能优化,光靠感觉不行,得有量化的基准数据。Go 标准库中的 testing 包天然支持基准测试,开发者只要写好以 Benchmark 开头的函数,就能让编译器帮你跑出每操作耗时、内存分配等关键指标。但很多人跑完测试,数据只在命令行一闪而过,既没有留存也没有横向对比,下次优化完根本不知道到底快了多少。其实 Go 的 go test 命令提供了一整套参数,既能精细控制基准过程,也能把结果和性能剖面保存到磁盘。

编写可运行的基准测试函数
基准测试函数和普通的单元测试很像,但签名必须是 func BenchmarkXxx(b *testing.B),并且一定要把被测试的逻辑放在 b.N 的循环里。框架会根据系统负载自动调整 b.N 的大小,使得基准跑足够长时间以获得稳定数据。如果循环外有昂贵的准备工作,应当用 b.ResetTimer() 把准备时间排除掉。
下面示例对一个字符串拼接函数做基准。注意我们先用 b.StopTimer() 准备数据,再 b.StartTimer() 正式计时,避免初始化干扰结果。这种写法在真实业务里很常见,比如提前生成万级元素的切片。
package benchdemo
import (
"strings"
"testing"
)
func concatWithPlus(n int) string {
s := ""
for i := 0; i < n; i++ {
s += "a"
}
return s
}
func BenchmarkConcatWithPlus(b *testing.B) {
b.StopTimer()
n := 1000
b.StartTimer()
for i := 0; i < b.N; i++ {
_ = concatWithPlus(n)
}
}
func BenchmarkConcatWithBuilder(b *testing.B) {
b.StopTimer()
n := 1000
b.StartTimer()
for i := 0; i < b.N; i++ {
var sb strings.Builder
for j := 0; j < n; j++ {
sb.WriteString("a")
}
_ = sb.String()
}
}
上面两段基准分别衡量加法拼接和 strings.Builder 的差别。运行后你能直观看到后者在分配次数上的优势。把这类函数统一放在 _test.go 文件中,既不影响生产代码,也能随时复跑。
使用 go test 获取基准数据
最基础的命令是 go test -bench=.,它会运行当前包下所有基准。但默认不显示内存指标,需要加上 -benchmem。如果想让结果更稳,可以用 -benchtime=3s 让每个基准至少跑三秒,或者用 -count=5 重复五次取趋势。
例如执行下面指令,就能在终端得到类似 BenchmarkConcatWithPlus-8 10000 123456 ns/op 1024 B/op 10 allocs/op 的输出。这里的 -8 是 GOMAXPROCS 值,ns/op 是单次操作纳秒数,B/op 和 allocs/op 则是内存分配量和次数。
go test -bench=. -benchmem -benchtime=3s -count=3 ./...
如果只关心某个函数,可以把正则写成 -bench=BenchmarkConcatWithBuilder。在 CI 里,我们通常把 -bench 和 -run=^$ 搭配,彻底跳过单元测试,只跑性能,节省流水线时间。
将基准结果保存到文件
最简单粗暴的保存方式就是重定向标准输出。因为 go test 的基准报告本来就是纯文本,直接写文件就能留存。配合 -benchout 参数(Go 1.17+ 支持 JSON 格式)还能得到结构化数据,方便后续程序解析。
下面两条命令分别保存文本和 JSON。文本适合人眼比对,JSON 适合自动化。注意 JSON 文件里包含了每次运行的明细,可以用 jq 或自写 Go 小工具提取耗时字段,再画曲线。
go test -bench=. -benchmem -benchtime=3s > bench_result.txt go test -bench=. -benchmem -benchtime=3s -benchout=bench_result.json
如果团队用 Git 管理代码,可以把 bench_result.txt 提交进仓库的 perf/ 目录,按日期或版本号命名。这样半年后还能翻出旧数据,确认某次重构是否真的带来了提升。不过文本对比靠肉眼累,更推荐用 benchmark 对比工具,比如 benchstat。
用 benchstat 做差异分析
benchstat 是 Go 官方维护的分析小工具,能读入多份基准文件并给出置信区间和快慢百分比。安装后用两条命令分别生成旧和新结果,再交给它比对。
go install golang.org/x/perf/cmd/benchstat@latest go test -bench=. -benchmem -benchtime=3s > old.txt # 改完代码后 go test -bench=. -benchmem -benchtime=3s > new.txt benchstat old.txt new.txt
输出会明确写出 ~ 1.20x 这类倍数变化,并标注是否显著。把它接入 MR 检查,就能防止有人提交让性能回退的代码。
保存 CPU 与内存性能剖面
除了文字结果,Go 还能在基准时顺带采集 profile。加 -cpuprofile=cpu.out 和 -memprofile=mem.out,测试结束会留下二进制剖面文件,用 go tool pprof 离线打开,能看到火焰图、调用栈和内存热点。
这种方式比单纯数字更进一步:当某次基准突然变慢,你不用猜,直接 pprof 一看就知道是哪个函数吃了 CPU。下面命令在跑基准同时产出两种剖面。
go test -bench=. -benchmem -cpuprofile=cpu.out -memprofile=mem.out -benchtime=5s go tool pprof cpu.out
剖面文件体积较大,不建议全量进 Git,可以传到大象存储或对象桶里,文件名带上 Git SHA。配合 Grafana 或自研看板,就能把每次提交的基准和剖面关联起来,形成完整的性能演进档案。
自动化保存与比对实践
在较大团队中,手动跑命令容易遗漏。我们通常用 Makefile 或 CI 脚本封装。下面给出一个极简的 Shell 片段,它在每次主干合并时跑基准、存 JSON、调 benchstat 并评论到提交。
#!/bin/sh SHA=$(git rev-parse HEAD) go test -bench=. -benchmem -benchtime=3s -benchout=perf/$SHA.json if [ -f perf/baseline.json ]; then benchstat perf/baseline.json perf/$SHA.json > perf/diff_$SHA.txt fi cp perf/$SHA.json perf/baseline.json
这段逻辑把当前结果始终作为下次的基线,长此以往就积累了连续数据。如果再补一段 Python 读 JSON 写 CSV,就能用 Excel 拉出耗时趋势线。本质上,Go 获取并保存基准测试结果并不复杂,难的是把它变成日常习惯,而不是性能出事才临时跑一下。
总结来说,从写对 Benchmark 函数,到用 -benchmem 拿全指标,再到重定向或 -benchout 落盘,以及用 pprof 留剖面,Go 给了一套开箱即用的方案。把这些命令编进脚本,性能优化就有据可依。
Gobenchmark_testingperformance_profile修改时间:2026-08-04 02:39:35