mcpField
G
golang-benchmark

golang-benchmark

github.com/samber开发3.7k39.9k 次安装5 天前更新
代码仓库

关于 golang-benchmark

面向 Go 开发者的性能测量技能,帮助获得可靠的基准结果,涵盖编写基准、性能分析、统计比较和 CI 回归检测。

主要功能

  1. 01基准编写与运行指南
  2. 02分配量和自定义指标测量
  3. 03CPU、内存与追踪分析
  4. 04使用 benchstat 进行统计比较
  5. 05CI 性能回归检测参考

适用场景

  1. 01优化前测量 Go 代码路径
  2. 02比较多次基准测试的变更
  3. 03排查 CPU 或内存开销
  4. 04在 CI 中检查性能回归

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/samber/cc-skills-golang --skill golang-benchmark

安装后在 Claude Code 中输入/golang-benchmark确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
references/
benchstat.md
ci-regression.md
compiler-analysis.md
investigation-session.md
pprof.md
prometheus-go-metrics.md
tools.md
trace.md

详细介绍

角色: 你是一名 Go 性能测量工程师。你绝不会根据单次基准测试得出结论——做出任何优化决策之前,都必须满足统计严谨性和受控条件。

思考模式: 对基准测试分析、性能剖析解读和性能比较任务进行尽可能透彻的推理——深入思考有助于避免误读性能剖析数据,并确保结论在统计上可靠。在 Claude Code 中,使用 ultrathink 显式触发扩展思考。

依赖项:

  • benchstat:go install golang.org/x/perf/cmd/benchstat@latest

Go 基准测试与性能测量

没有测量,就无从谈起性能提升——只要能测量,就能改进。

本技能涵盖完整的测量工作流:编写基准测试、运行基准测试、分析结果、以统计严谨的方式比较优化前后表现,并在 CI 中跟踪回归。如需了解测量后可采用的优化模式,→ 请参阅 samber/cc-skills-golang@golang-performance 技能。如需了解运行中服务的 pprof 设置,→ 请参阅 samber/cc-skills-golang@golang-troubleshooting 技能。

编写基准测试

文件与顺序约定

基准测试函数应放在 _bench_test.go 文件中,文件名以被测源文件命名,而不是以单个函数命名——例如,parser.go -> parser_bench_test.go,其中包含 BenchmarkParse、BenchmarkEncode 等,而不是为每个函数单独创建一个 benchmarkparse_test.go。

  • 将基准测试放在独立文件中(而不是混在 parser_test.go 中),可以让 go test -bench=. ./pkg/parser 的输出不受无关 Test* 噪声影响。
  • 这样可以将为测量准备的测试夹具(大输入、长生命周期的初始化)与为验证正确性准备的测试夹具分开——二者很少具有相同的形式。
  • 该文件仍遵循 Go 的每个源文件对应一个测试文件的约定(→ 请参阅 samber/cc-skills-golang@golang-testing 技能),只是用 _bench 后缀标明其更具体的用途。

在 parser_bench_test.go 中,按 parser.go 里被测函数或方法的顺序排列 Benchmark* 函数——读者从头到尾对照这两个文件时,应能在 Parse 所在的相对位置找到 BenchmarkParse。

b.Loop()(Go 1.24+)——首选

对于 Go 1.24+,编写新的基准测试时优先使用 b.Loop()。它只对循环体计时,并确保函数参数和结果保持存活,从而减少死代码消除造成的错误。

func BenchmarkParse(b *testing.B) {
    data := loadFixture("large.json") // setup — excluded from timing
    for b.Loop() {
        Parse(data)  // compiler cannot eliminate this call
    }
}

保留现有基准测试或支持 Go <1.24 时,传统的 b.N 循环仍可编译,也可以继续使用。但它更容易出错:可能需要通过 b.ResetTimer() 处理初始化,也可能需要使用结果接收器,以防编译器消除相关工作。Go 1.26 修复了 b.Loop() 早期版本中的内联限制——Go 1.24–1.25 上的基准测试已经能从 b.Loop() 中受益,但可能无法获得 Go 1.26 带来的内联优化。

Go 1.27 的大小专用分配器会改变分配密集型基准测试的基线(小于 80 字节的分配更快,二进制文件更大),这与代码改动无关。如果 benchstat 比较跨越 Go 1.26→1.27 工具链边界,那么测量的就是工具链,而不是代码——在信任性能差异之前,应使用与“优化后”相同的工具链重新运行“优化前”基准测试。

内存跟踪
func BenchmarkAlloc(b *testing.B) {
    b.ReportAllocs() // or run with -benchmem flag
    var sink []byte
    for b.Loop() {
        sink = make([]byte, 1024)
    }
    _ = sink
}

b.ReportMetric() 可添加自定义指标(例如吞吐量):

b.ReportMetric(float64(totalBytes)/b.Elapsed().Seconds(), "bytes/s") // b.Elapsed() is only valid inside b.Loop()
子基准测试与表驱动
func BenchmarkEncode(b *testing.B) {
    for _, size := range []int{64, 256, 4096} {
        b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) {
            data := make([]byte, size)
            for b.Loop() {
                Encode(data)
            }
        })
    }
}

运行基准测试

go test -bench=BenchmarkEncode -benchmem -count=10 ./pkg/... | tee bench.txt
标志用途
-bench=.运行所有基准测试(正则表达式筛选)
-benchmem报告内存分配情况(B/op、allocs/op)
-count=10运行 10 次,以获得统计显著性
-benchtime=3s每项基准测试的最短运行时间(默认为 1s)
-cpu=1,2,4使用不同的 GOMAXPROCS 值运行
-cpuprofile=cpu.prof写入 CPU 性能分析文件
-memprofile=mem.prof写入内存性能分析文件
-trace=trace.out写入执行跟踪文件

输出格式: BenchmarkEncode/size=64-8 5000000 230.5 ns/op 128 B/op 2 allocs/op——后缀 -8 表示 GOMAXPROCS,ns/op 表示每次操作耗时,B/op 表示每次操作分配的字节数,allocs/op 表示每次操作的堆分配次数。

并行比较优化方案

当同一瓶颈存在多个相互竞争的优化假设时,通过不同的子 Agent,在各自独立的 worktree 中实现每个方案,避免代码更改在共享工作树中发生冲突。

基准测试应串行运行,不要并行运行。 并行运行的基准测试会共享同一 CPU——邻居噪声效应会污染 ns/op,并重新引入 -count 和 benchstat 本应消除的统计噪声。并行实现是安全的(worktree 相互隔离,不会争用文件);并行测量则不安全(共享硬件,存在实际争用)。一次只运行一个方案的基准测试,可在主工作树中运行,也可在各 worktree 中依次运行。

将每个方案的 benchstat 输出都与同一份基线报告进行比较,保留表现最佳的方案,并移除其余方案的 worktree。

在提交中记录结果

如果更改对性能有可测量的影响,请在提交正文中粘贴 benchstat 输出。这样可以记录优化的_原因_,防止后续读者将其撤销,还能让审阅者无需重新运行基准测试即可验证结论。

提交格式:

perf(parser): reduce Parse allocations 50% with sync.Pool

Replace per-call []byte allocation with a pooled buffer.

goos: linux / goarch: amd64 / cpu: AMD Ryzen 9 5950X
          │    old     │              new               │
          │  sec/op    │  sec/op     vs base            │
Parse-32    4.592µ ± 2%  3.041µ ± 1%  -33.78% (p=0.000 n=10)

          │   old    │             new              │
          │   B/op   │   B/op     vs base           │
Parse-32   1.024Ki ± 0%  0.512Ki ± 0%  -50.00% (p=0.000 n=10)

          │ old  │            new             │
          │ allocs/op │ allocs/op  vs base    │
Parse-32   12.00 ± 0%   6.000 ± 0%  -50.00% (p=0.000 n=10)

规则:

  • 仅包含受更改直接影响的基准测试——删除无关行
  • 不要粘贴带有 ~ 的结果(统计上不显著)——不能据此宣称有改进
  • 包含硬件环境行(goos/goarch/cpu),以便复现结果
  • 仅涉及性能的更改使用 perf(scope): 提交类型

从基准测试生成性能分析数据

直接从基准测试运行中生成性能分析文件——无需 HTTP 服务:

# CPU profile
go test -bench=BenchmarkParse -cpuprofile=cpu.prof ./pkg/parser
go tool pprof cpu.prof

# Memory profile (alloc_objects shows GC churn, inuse_space shows leaks)
go test -bench=BenchmarkParse -memprofile=mem.prof ./pkg/parser
go tool pprof -alloc_objects mem.prof

# Execution trace
go test -bench=BenchmarkParse -trace=trace.out ./pkg/parser
go tool trace trace.out

完整的 pprof CLI 参考(所有命令、非交互模式、性能分析结果解读),请参阅 pprof 参考。执行跟踪结果的解读,请参阅 Trace 参考。统计比较,请参阅 benchstat 参考。

参考文件

  • pprof 参考 — 交互式和非交互式分析 CPU、内存和 goroutine 配置文件。涵盖完整的 CLI 命令、配置文件类型(CPU、alloc*objects 和 inuse_space)、Web UI 导航以及解读模式。需要深入了解代码中时间和内存都花在了哪里时,请使用此参考。

  • benchstat 参考 — 使用严格的置信区间和 p 值检验,对基准测试运行结果进行统计比较。涵盖如何读取输出、筛选旧基准测试、交错结果以便直观比较,以及检测性能回归。需要证明某项更改确实带来了有意义的性能差异,而不只是一次幸运的测试结果时,请使用此参考。

  • Trace 参考 — 用于了解代码何时以及为何运行的执行跟踪器。可视化 goroutine 调度、垃圾回收阶段、网络阻塞和自定义 span 注释。pprof(显示 CPU 用在了哪里)不够用、还需要查看事件时间线时,请使用此参考。

  • 诊断工具 — 辅助工具速查:fieldalignment(结构体填充浪费)、GODEBUG(运行时日志标志)、fgprof(帧图配置文件)、竞态检测器(并发错误)等。遇到特定症状并需要有针对性地诊断时,请使用此参考;如果更简单的工具已经能回答问题,就不必使用 pprof。

  • 编译器分析 — 编译器优化的底层洞察:逃逸分析(值何时移到堆上)、内联决策(哪些函数调用会被消除)、SSA 转储(中间表示)和汇编输出。基准测试显示存在意料之外的分配,或想验证编译器是否按预期工作时,请使用此参考。

  • CI 回归检测 — 在 CI 流水线中自动检测性能回归并进行门控。涵盖三种工具(用于快速 PR 比较的 benchdiff、基于严格阈值进行门控的 cob、用于长期趋势仪表板的 gobenchdata)、缓解吵闹邻居影响的策略(为什么即使在空闲机器上,云端 CI 基准测试结果也会有 5-10% 的波动),以及让基准测试结果可复现的自托管运行器调优。希望确保拉取请求不会悄悄拖慢代码库时,请使用此参考;及早检测回归可以避免累积性能债务。

  • 调查会话 — 生产环境性能排查工作流,结合 Prometheus 运行时指标(堆大小、GC 频率、goroutine 数量)、用于关联指标和代码更改的 PromQL 查询、运行时配置标志(用于启用 GC 日志的 GODEBUG 环境变量)以及成本警告(何时正在承受性能开销)。生产环境基准测试表现良好,但实际流量行为不同时,请使用此参考。

  • Prometheus Go 指标参考 — 完整列出 prometheus/client_golang 实际公开为 Prometheus 指标的 Go 运行时指标。涵盖 30 个默认指标、40 多个可选指标(Go 1.17+)、进程指标以及常见的 PromQL 查询。区分 runtime/metrics(Go 内部数据)和 Prometheus 指标(从 /metrics 抓取的数据)。设置监控仪表板或编写生产环境告警的 PromQL 查询时,请使用此参考。

交叉引用

  • → 请参阅 samber/cc-skills-golang@golang-performance 技能,了解测量后可应用的优化模式(“如果 X 是瓶颈,则应用 Y”)
  • → 请参阅 samber/cc-skills-golang@golang-troubleshooting 技能,了解运行中服务的 pprof 设置(启用、保护、采集)、Delve 调试器、GODEBUG 标志和根因分析方法
  • → 请参阅 samber/cc-skills-golang@golang-observability 技能,了解日常持续监控、持续性能分析(Pyroscope)和分布式追踪(OpenTelemetry)
  • → 请参阅 samber/cc-skills-golang@golang-testing 技能,了解通用测试实践
  • → 请参阅 samber/cc-skills@promql-cli 技能,了解如何查询生产环境中的 Prometheus 运行时指标,以验证基准测试结果

评论

支持 Markdown