主题
Go 性能诊断工具选型:六个工具,六种不同的问题
“给我一张火焰图”还不是一个足够明确的诊断请求。CPU 的火焰图、fgprof 的墙钟采样图和 heap 的分配图,宽度表示的东西不同;一段耗时很长的等待,在 CPU 图里可能完全不宽。选工具前,先写下要证实的判断:哪里耗 CPU、哪里等待、谁没退出、这次改动是否更快,还是某个变量为什么错了。
本文逐一说明 pprof、benchstat、dlv、fgprof、goleak 与 go tool trace,并用同一份本地工作负载比较 CPU 与墙钟采样。所有测量都限制在本地短命进程,没有生产压测、远程 attach 或公开诊断服务。
用问题选入口
| 问题 | 首选工具 | 输入与输出 | 不适合独自回答 |
|---|---|---|---|
| CPU、分配或锁等待的热点在哪? | pprof | Go profile 文件 → top、调用图、火焰图 | 任意变量值、完整时间先后 |
| 两版改动是否有可复核差异? | benchstat | 重复基准文本 → 中位数、区间、A/B 比较 | 一次线上慢请求为什么慢 |
| 某一行的状态与控制流是什么? | dlv | 活进程或匹配 core → 断点、栈、变量 | 无干扰的生产延迟测量 |
| CPU 与不占 CPU 的等待,主要落在哪些栈? | fgprof | 周期性 goroutine 栈采样 → pprof/folded 文件 | 精确 CPU 消耗、每个请求的因果时间线 |
| 测试结束后哪些 goroutine 没结束? | goleak | 测试进程的 goroutine 栈 → 泄漏诊断 | 对未执行路径证明“没有泄漏” |
| 被唤醒后何时运行,GC/阻塞发生在何时? | go tool trace | runtime trace → 时间线、调度等摘要 | 远端数据库执行计划、所有业务函数耗时 |
可以组合使用,但不必同时采集。全量内存采样会改变分配成本,block 采样会影响调度,调试断点直接暂停进程。先用最便宜的证据缩小问题,再分开做更有针对性的采集。[1]
正在绘制图示…
查看图源代码
flowchart LR
Q[把问题写成可检验的判断] --> R{主要缺哪种证据?}
R -->|资源分布| P[pprof]
R -->|混合计算与等待的栈| F[fgprof]
R -->|先后与唤醒关系| T[go tool trace]
R -->|当前变量与分支| D[dlv]
R -->|结束后仍活着的任务| G[goleak]
R -->|改动前后统计| B[benchstat]
P --> M[形成假设并修改]
F --> M
T --> M
D --> M
G --> M
M --> B
本文固定的版本
| 工具 | 本次版本 | 验证深度 |
|---|---|---|
| pprof、go tool trace | Go 1.27.2 随附工具 | 真实采集与命令行解析 |
| benchstat | x/perf v0.0.0-20261009192801-be2c69fb417e | 安装、10 份真实基准摘要 |
| dlv | v1.27.2 | 安装、version、help;无断点/attach/core 实测 |
| fgprof | v0.9.5 | 实际 API 编译、采集、pprof 解析、路由隔离测试 |
| goleak | v1.3.0 | 检出受控未退出 worker,再清理并验证无残留 |
fgprof 的传递依赖 github.com/google/pprof 也固定为 v0.0.0-20240227163752-401108e1b7e7,完整 go.mod/go.sum 在文末。版本固定是为了复现,不能替代后续升级时的兼容和安全检查。
pprof:强项是资源归因,先选 sample type
pprof 既可以指 Go 的采集 API,也可以指分析器。runtime/pprof.StartCPUProfile/StopCPUProfile 采 CPU;heap、allocs、goroutine、block、mutex 等用内置 profile 导出;go tool pprof 分析文件。heap 默认看存活字节,allocs 默认看历史分配字节;block 看等待方,mutex 通常看引起竞争的临界区结束处,而且二者需要启用采样。[2]
本例一轮任务先 sleep 120 ms,再进行约 60 ms 的计算,共做两轮。waitIO 只是模拟不占 CPU 的等待,没有真的访问网络。CPU 采集在独立进程中运行,真实报告是:
text
Type: cpu
Duration: 360.82ms, Total samples = 110ms (30.49%)
110ms 100% main.calculate因此 calculate 占 CPU 样本 100%,但它没有占完整请求墙钟 100%。少掉的等待时间不意味着 CPU profiler 错,它本来就不负责统计 sleep 的墙钟等待。
bash
go tool pprof -top out/lab out/cpu.out先看 flat(函数自身),再看 cum(含子调用)。火焰图宽度是选中指标的样本权重,横向不是时间轴。要看调用先后,换 trace;要看某个变量,换调试器。采样成本随 profile 类型、采样率和工作量变化,不存在所有服务都适用的“零开销”。[1][2]
fgprof:把不占 CPU 的等待也带进来
fgprof v0.9.5 在后台 goroutine 里按目标 99 Hz 取 runtime.GoroutineProfile,记录正在运行和正在等待的 goroutine 栈,再导出墙钟采样权重。这个实现与 CPU profiler 的采样机制不同,因此不要把两者数字直接相减当作精确的 I/O 时间。[3]
最小本地入口是:
go
stop := fgprof.Start(f, fgprof.FormatPprof)
// 运行有界工作负载
if err := stop(); err != nil { return err }停止函数会完成导出,应调用一次并处理错误,然后关闭文件。本例在另一个进程运行相同结构的工作负载,采得:
text
Type: time
Duration: 361.68ms, Total samples = 360.82ms (99.76%)
main.waitIO cum=237.11ms 65.71%
main.calculate cum=123.71ms 34.29%上面两行摘出 cum 列并重新排版,完整 top 保留在复现输出。现在等待路径出现了,粗略比例与两轮 120 ms 等待、60 ms 计算相符。但具体几十微秒或某条 CPU 指令的归因不应靠它判断。本次 flat 里还出现 time.Now、runtime.asyncPreempt2,进一步说明这是取栈时的观测,不是逐指令的 CPU 计费。
正在绘制图示…
查看图源代码
flowchart LR
W[同一结构的本地任务<br/>等待 120ms + 计算 60ms,重复两轮] --> C[CPU profiler<br/>只统计正在消耗 CPU 的栈]
W --> F[fgprof<br/>周期性观察运行与等待的 goroutine 栈]
C --> CR[本次 calculate 110ms CPU 样本]
F --> FR[本次 waitIO 237.11ms<br/>calculate 123.71ms 墙钟权重]
图中的两个采集是分开运行,不能用它们衡量“开启 fgprof 多花了多少 CPU”。如果要测开销,需要另外设计同等条件下开与关的重复比较。
fgprof 的代价随 goroutine 和栈增长
它要反复遍历 goroutine 栈,goroutine 越多、不同栈越多,采样与存储成本越需要关注。采样 goroutine 自己也可能调度延迟;上游源码会按实际采样次数调整频率估计,但不能保证消掉所有偏差。大量空闲 worker 也会贡献等待权重,所有 goroutine 的权重相加可以超过采集窗口,所以不能默认每张图都代表单个请求。[3]
v0.9.5 用固定长度栈记录聚合,深调用栈与版本相关的栈展开行为也应验证。本文小例子可成功解析,不构成对所有 Go 1.27.2 应用栈形状的全面验证。
HTTP 入口是 fgprof.Handler(),当前参数 seconds 默认 30,format 默认 pprof,也支持 folded。它不替你提供认证、并发采集限制或生产预算。示例只注册在单独管理 mux:
go
m := http.NewServeMux()
m.Handle("GET /debug/fgprof", fgprof.Handler())测试断言管理路由 POST 返回 405、全新的业务 mux 上 GET 返回 404。本文没真正开启 HTTP listener。上线还要加管理网络、身份校验、时长上限、并发限制和产物访问控制,不要把上游最短演示里的公开端口直接照搬。[3]
go tool trace:需要时间与因果时再用
runtime/trace.Start/Stop 记录 goroutine 状态转换、系统调用、GC、P 活动等运行时事件;go tool trace 解析。它能区分 Waiting、被唤醒后的 Runnable、真正 Running,适合调查调度延迟、GC 停顿和同步等待。task/region/log 可进一步把事件和业务操作联系起来。[4]
同一程序的第三个独立进程采集 trace,实际执行:
bash
go tool trace -pprof=sched out/trace.out > out/sched.pprof
go tool pprof -top out/sched.pprof本次成功得到 Type: delay 的调度摘要,总量约 350.21 μs。它不是那 240 ms 的 sleep 总量,因为 sched 摘要统计的是可运行之后等执行的调度延迟,而不是所有墙钟等待。工具还可导出 net、sync、syscall 汇总;交互时间线通过 go tool trace -http=127.0.0.1:8081 out/trace.out 查看。[4]
trace 记录事件的数量通常远大于一个聚合 profile,文件规模、内存和解析成本也随活动增长。限制窗口、保护标记中的业务数据,并用相应工具链解析。本篇验证了离线导出,没有声称打开浏览器渲染时间线,也没有把进程内 trace 说成自动跨服务链路追踪。
goleak:把“关闭之后该退出”写成测试约束
goleak 检查测试进程里是否留下非预期 goroutine。常用方式是在单个串行测试末尾 goleak.VerifyNone(t);整个套件可用 goleak.VerifyTestMain(m)。先让组件有明确的 stop/cancel/Close,再等待它退出,否则工具只能指出没结束,不能替你设计生命周期。[5]
本例故意启动一个等 channel 的 worker,先让 goleak.Find() 返回诊断,再关闭 channel、等待 done,最后 VerifyNone:
go
stop := make(chan struct{})
done := make(chan struct{})
go leakedWorker(stop, done)
err := goleak.Find() // 此时应检出未退出 worker
close(stop)
<-done
goleak.VerifyNone(t)实际测试日志:
text
TestLeakDetectedAndCleaned
leakedWorker detected=true
PASS完整测试不仅检查 err 非 nil,还检查诊断包含 leakedWorker,避免把其他后台活动误当作目标反例。故意留下的 worker 随后确实清理,因此测试成功不靠真的泄漏到进程退出。另一测试验证 context 取消后 worker 在限定时间内退出。普通与 race 测试各重复两次,都通过。
不要在相互并行的每个测试里随意 VerifyNone:别的测试正在运行的 goroutine 可能造成误报。并行套件更适合在 TestMain 的整体结束点检查。IgnoreTopFunction、IgnoreAnyFunction 与 IgnoreCurrent 都会扩大忽略范围,只针对确实长寿、由其他生命周期管理的组件使用,并留下理由。把业务 worker 全部忽略,会让测试失去意义。[5]
goleak 要取栈,并会给正常退出留出重试等待,失败测试会更慢;本例检出阶段每次约 0.45 秒。它也不证明未运行的请求路径安全,更不等同于数据竞争检测或生产泄漏监控。
benchstat:比较的是重复测量,不会修复错误实验
benchstat 读取 go test -bench 格式文本。默认给中位数与置信区间;输入多个可比版本时,会做统计比较。先确保输入、工具链、CPU 配置与测试语义相同,再保留足够多样本、交错前后运行,不要筛选最好看的几行。[6]
为了独立复现,本文附一个求和基准:对同一份 1024 个整数,用 b.Loop 保留被测计算,输入准备在循环前。Go 1.27.2 支持 B.Loop 的当前防消除语义。标准优化构建、AMD EPYC 9V74、GOMAXPROCS=1、100 ms/样本,共 10 份,实际摘要:
text
Add 608.4 ns/op ± 5%
0 B/op
0 allocs/op这里只给单组摘要,没有第二种实现,所以不存在“比旧版显著更快”的结论。A/B 使用方式是 benchstat before.txt after.txt,两个文件里必须真的是同名、同工作量基准。默认统计是非参数摘要与 Mann–Whitney U 检验,~ 表示没检出显著差异,不能说两者已经被证明相等;显著也不表示收益在业务上够大。[6]
ns/op 是操作平均值,不能直接当 p99。共享机器上的短微基准容易受其他任务影响;race 或调试构建的数字不能和普通优化构建混比。工具成本主要发生在测试工作量与统计分析阶段,不必把 benchstat 接进运行中的服务。
dlv:当你真正需要看变量时
Delve 可以断点、条件断点、单步、选择 goroutine、查看调用栈与变量,也能用匹配二进制离线检查受支持的 core。典型入口是 dlv debug .、dlv exec ./app.debug、dlv core ./app ./core.file。这与采样器最大的区别,是它能暂停和控制目标,因而也最容易改变时序。[7]
本地逻辑调试常用 go build -gcflags='all=-N -l' 构建;不能把这个调试构建当性能基线。优化构建的变量可能被消除或移到难以呈现的位置。查看 core 要用原始二进制,不能重新编一个同名程序去匹配旧地址。
本次只实际核对了:
text
Delve Debugger
Version: 1.27.2version/help 成功不等于有目标进程调试权限。本环境不允许 ptrace,没有尝试 attach、运行断点或打开 core。生产使用前必须明确暂停影响、权限与恢复方式;core 和调试端口可能暴露整个进程的秘密,不能当普通性能图随意分享。[7]
如何组合,什么时候收手
一个常用顺序是:监控确认症状 → pprof/fgprof 缩小资源或等待路径 → trace 澄清时序,或在授权副本上用 dlv 查状态 → 用测试与 goleak 固定生命周期 → 用重复基准和代表性系统负载验证修改。
不是每次都走完整条路线。找到“未关闭的 Rows 导致连接池排队”,就应修复归还路径并复验;再追一个无关纳秒热点只会分散精力。完成条件也应具体:相同场景的结果正确,目标指标改善,goroutine/资源能回落,没有把 CPU 成本换成不可接受的内存或尾延迟。
依据
- Go diagnostics:各诊断类别与工具互相干扰。
- Go 1.27.2 runtime/pprof:各 profile、采样与归因。
- fgprof v0.9.5、采样源码、HTTP handler:99 Hz、GoroutineProfile、格式、生命周期与参数。
- Go 1.27.2 runtime/trace、cmd/trace:事件与 CLI 汇总;本次实际导出验证。
- goleak v1.3.0 API、固定实现:Find、VerifyNone、VerifyTestMain、忽略与重试。
- benchstat 官方文档、固定提交 be2c69fb417e;Go 1.27.2 testing。
- Delve v1.27.2 官方文档:断点、exec、core 与版本限制。
完整复现文件与命令
需要 Go 1.27.2、Linux 和 GNU timeout。先按下列 go.mod/go.sum 下载固定依赖,之后测试与采样都在本地,不请求任何业务网站:
bash
GOPROXY=https://proxy.golang.org GOSUMDB=sum.golang.org go mod download
bash run.sh out
mkdir -p bin
GOBIN="$PWD/bin" GOPROXY=https://proxy.golang.org GOSUMDB=sum.golang.org \
go install golang.org/x/perf/cmd/benchstat@v0.0.0-20261009192801-be2c69fb417e
./bin/benchstat out/bench.txtDelve 版本/帮助检查独立于上述实验,没有启动目标程序:
bash
GOBIN="$PWD/bin" GOPROXY=https://proxy.golang.org GOSUMDB=sum.golang.org \
go install github.com/go-delve/delve/cmd/dlv@v1.27.2
./bin/dlv version
./bin/dlv help exec
./bin/dlv help corego.mod
go.mod
module example.com/toolslab
go 1.27.2
require (
github.com/felixge/fgprof v0.9.5
go.uber.org/goleak v1.3.0
)
require github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirectgo.sum
代码块 · 36 行 · 点击收起 / 展开
text
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=
github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=main.go
代码块 · 84 行 · 点击收起 / 展开
go
package main
import (
"fmt"
"github.com/felixge/fgprof"
"net/http"
"os"
"runtime/pprof"
"runtime/trace"
"time"
)
var value uint64
//go:noinline
func calculate() {
until := time.Now().Add(60 * time.Millisecond)
x := uint64(1)
for time.Now().Before(until) {
for range 10000 {
x = x*1664525 + 1013904223
}
}
value = x
}
//go:noinline
func waitIO() { time.Sleep(120 * time.Millisecond) }
func run(mode, path string) error {
f, err := os.Create(path)
if err != nil {
return err
}
var stop func() error
switch mode {
case "fgprof":
stop = fgprof.Start(f, fgprof.FormatPprof)
case "cpu":
if err := pprof.StartCPUProfile(f); err != nil {
f.Close()
return err
}
stop = func() error { pprof.StopCPUProfile(); return nil }
case "trace":
if err := trace.Start(f); err != nil {
f.Close()
return err
}
stop = func() error { trace.Stop(); return nil }
default:
f.Close()
return fmt.Errorf("unknown mode %q", mode)
}
for range 2 {
waitIO()
calculate()
}
err = stop()
closeErr := f.Close()
if err != nil {
return err
}
if closeErr != nil {
return closeErr
}
fmt.Println(mode, "capture complete")
return nil
}
func main() {
if len(os.Args) != 3 {
fmt.Fprintln(os.Stderr, "usage: lab fgprof|cpu|trace output")
os.Exit(2)
}
if err := run(os.Args[1], os.Args[2]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func diagnosticsMux() *http.ServeMux {
m := http.NewServeMux()
m.Handle("GET /debug/fgprof", fgprof.Handler())
return m
}main_test.go
代码块 · 82 行 · 点击收起 / 展开
go
package main
import (
"context"
"go.uber.org/goleak"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
//go:noinline
func leakedWorker(stop <-chan struct{}, done chan<- struct{}) { <-stop; close(done) }
func TestLeakDetectedAndCleaned(t *testing.T) {
stop := make(chan struct{})
done := make(chan struct{})
go leakedWorker(stop, done)
err := goleak.Find()
if err == nil {
close(stop)
<-done
t.Fatal("expected diagnostic")
}
if !strings.Contains(err.Error(), "leakedWorker") {
close(stop)
<-done
t.Fatal(err)
}
t.Log("leakedWorker detected=true")
close(stop)
<-done
goleak.VerifyNone(t)
}
func TestWorkerStops(t *testing.T) {
defer goleak.VerifyNone(t)
ctx, cancel := context.WithCancel(context.Background())
done := make(chan struct{})
go func() { defer close(done); <-ctx.Done() }()
cancel()
select {
case <-done:
case <-time.After(time.Second):
t.Fatal("worker did not stop")
}
}
func add(values []int) int {
total := 0
for _, v := range values {
total += v
}
return total
}
func TestAdd(t *testing.T) {
if add([]int{1, 2, 3}) != 6 {
t.Fatal("add")
}
}
func BenchmarkAdd(b *testing.B) {
xs := make([]int, 1024)
for i := range xs {
xs[i] = i
}
b.ReportAllocs()
for b.Loop() {
_ = add(xs)
}
}
func TestRouting(t *testing.T) {
w := httptest.NewRecorder()
diagnosticsMux().ServeHTTP(w, httptest.NewRequest("POST", "/debug/fgprof", nil))
if w.Code != 405 {
t.Fatal(w.Code)
}
w = httptest.NewRecorder()
http.NewServeMux().ServeHTTP(w, httptest.NewRequest("GET", "/debug/fgprof", nil))
if w.Code != 404 {
t.Fatal(w.Code)
}
}run.sh
bash
#!/usr/bin/env bash
set -euo pipefail
export TZ=UTC
out=${1:-out}
mkdir -p "$out"
{ go version; go env GOOS GOARCH; grep -m1 'model name' /proc/cpuinfo; } > "$out/environment.txt"
go test -count=2 -v . > "$out/test.txt"
go test -race -count=2 . > "$out/race.txt"
go vet . > "$out/vet.txt"
go build -o "$out/lab" .
for mode in fgprof cpu trace; do
timeout 5s "$out/lab" "$mode" "$out/$mode.out" > "$out/$mode-run.txt"
done
for mode in fgprof cpu; do
go tool pprof -top "$out/lab" "$out/$mode.out" > "$out/$mode-top.txt"
done
go tool trace -pprof=sched "$out/trace.out" > "$out/sched.pprof"
go tool pprof -top "$out/sched.pprof" > "$out/sched-top.txt"
go test -run='^$' -bench='^BenchmarkAdd$' -benchmem -benchtime=100ms -count=10 -cpu=1 > "$out/bench.txt"
go version -m "$out/lab" > "$out/modules.txt"本次十份原始求和基准
text
BenchmarkAdd 178407 659.7 ns/op 0 B/op 0 allocs/op
BenchmarkAdd 206732 614.2 ns/op 0 B/op 0 allocs/op
BenchmarkAdd 197844 610.9 ns/op 0 B/op 0 allocs/op
BenchmarkAdd 186723 636.1 ns/op 0 B/op 0 allocs/op
BenchmarkAdd 183027 615.4 ns/op 0 B/op 0 allocs/op
BenchmarkAdd 206557 596.2 ns/op 0 B/op 0 allocs/op
BenchmarkAdd 197410 594.9 ns/op 0 B/op 0 allocs/op
BenchmarkAdd 196628 604.1 ns/op 0 B/op 0 allocs/op
BenchmarkAdd 209517 601.1 ns/op 0 B/op 0 allocs/op
BenchmarkAdd 186458 605.9 ns/op 0 B/op 0 allocs/op