登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go问答

Go trace 和 pprof 应该先看哪一个

来源:17golang原创

时间:2026-10-23 03:37:09 179浏览 收藏

Go trace 和 pprof 没有固定的先后顺序:CPU 持续偏高、分配量明显增加,通常先看对应的 pprof profile;请求很慢但 CPU 不高、怀疑调度等待或并行任务没有充分运行,优先采集覆盖异常窗口的 execution trace。若已经明确怀疑锁或 channel 阻塞,block、mutex profile 也可以直接给出聚合线索,不必先绕一遍 CPU profile。

官方地址:https://go.dev/doc/diagnostics

pprof 更适合回答“成本累计在哪些调用栈”,trace 更适合回答“运行与等待如何发生”。CPU profile 不是完整请求耗时表,execution trace 也不是跨服务调用链追踪。

工具能力的差别,决定了第一份材料

这里讨论的是两种诊断能力,不是宣布某个版本新发布了功能。pprof 把采样成本归到调用栈,便于从 top 和调用关系中缩小热点;runtime/trace 记录运行时事件,适合观察 goroutine 的运行、阻塞、调度以及 GC 等现象。它们是互补材料,不是同一份数据的两种皮肤。

已观察到的症状先取得的材料要回答的问题
CPU 持续偏高CPU profile哪些调用栈消耗 CPU
分配频繁alloc_space / alloc_objects哪些位置产生分配压力
存活堆持续增长inuse_space / inuse_objects存活对象的分配位置
低 CPU、高延迟或并行度异常短窗口 execution trace运行与等待之间的关系
明确怀疑同步竞争block 或 mutex profile阻塞或竞争成本归属
性能症状与 CPU、内存及执行追踪材料的关系
图1:诊断选择结构图。关系表示症状与材料匹配,不是执行顺序,也不含实测性能数据。

为什么只看 CPU 火焰图容易选错方向?

一个接口慢,可能在等数据库、网络、锁、限流槽位,也可能只是排队。CPU profile 中某个函数占比较高,不等于它占用了相同比例的请求墙钟时间;进程大部分时间不在使用 CPU 时,少量真正运行的代码仍然会在 CPU 样本中成为“第一名”。

反过来,trace 可以展示等待和调度现象,但不自动解释远端数据库执行计划,也不能把每个运行时事件都映射为某个业务请求。需要按目标任务补充日志、指标或 trace 的 task / region 标记;跨进程分析还需要分布式追踪。不要因为两者都叫 trace,就期待 go tool trace 自动给出完整 RPC 调用链。

对内存也要先选对口径:alloc_space 是累计分配,不是现在仍占着的堆;inuse_space 是存活分配的统计,不是进程 RSS。看到分配热点后,还需判断对象是否被长期保留,不能只凭一个高排名就宣布发现泄漏。

先用一个固定编码任务熟悉材料

下面是本次原创的最小基准,只编码固定的目录条目,不包含网络、锁竞争或故意阻塞,因此用于熟悉 CPU 与分配归因,而非演示真实线上高延迟。将它保存为全新目录中的 encode_test.go;文章没有执行代码,也没有生成任何实测 profile 或耗时排名。

package probe

import (
    "encoding/json"
    "io"
    "testing"
)

type CatalogRow struct {
    Code  int    `json:"code"`
    Label string `json:"label"`
}

func BenchmarkEncodeCatalog(b *testing.B) {
    // 固定输入,准备数据不计入基准计时。
    rows := make([]CatalogRow, 2048)
    for i := range rows {
        rows[i] = CatalogRow{Code: i, Label: "sample-item"}
    }
    encoder := json.NewEncoder(io.Discard)
    b.ReportAllocs()
    b.ResetTimer()
    for i := 0; i 

新目录没有 go.mod 时先初始化模块;已有模块不要重复初始化。采用同一个测试二进制,分开运行采集,避免把开启多种诊断时的结果当作无干扰基线。

# 仅在新建的示例目录中初始化,然后构建固定测试二进制
go mod init example.com/probe
go test -c -o encode.test
# 单独采集 CPU;跳过普通测试,只运行指定基准
./encode.test -test.run='^$' -test.bench='^BenchmarkEncodeCatalog$' -test.benchtime=2s -test.cpuprofile=cpu.pprof
# 单独采集内存 profile;保留默认采样,不把它当成逐次分配日志
./encode.test -test.run='^$' -test.bench='^BenchmarkEncodeCatalog$' -test.benchtime=2s -test.memprofile=mem.pprof
# 单独采集执行追踪;短窗口示例不等于线上通用采集时长
./encode.test -test.run='^$' -test.bench='^BenchmarkEncodeCatalog$' -test.benchtime=2s -test.trace=trace.out

命令预期分别写出 CPU、内存和执行追踪文件,但具体占比、采样数和耗时取决于环境及工具链,不在这里预填。基准框架会做校准,因此 benchtime 不是整个进程或完整 trace 的严格总时长;分析时仍要关注真正执行目标负载的部分。

# 在同一目录查看累计 CPU 与分配成本的调用栈排名
go tool pprof -top ./encode.test cpu.pprof
go tool pprof -top -alloc_space ./encode.test mem.pprof
# 由读者在本机查看执行追踪;不要把查看入口公开到外网
go tool trace trace.out
原创基准、测试二进制与三类采集文件的静态组成
图2:诊断材料结构图。固定二进制和三种文件有不同用途,图中不展示虚构运行结果。

从聚合热点转向等待分析时保留哪些条件?

假设真实问题是批量导出时延迟突然升高,先用监控确认 CPU、分配、请求量和异常窗口。如果 CPU 热点能够解释资源饱和,就围绕热点做单变量修改;如果 CPU 并不忙且已有线索指向等待,采集该窗口的短 trace,比盯着 CPU top 第一行反复优化更有针对性。这是排查建议,不是本例基准的实验结论。

切换工具不应顺便换了输入规模、并发数和运行阶段。至少记录 Go 工具链、二进制、GOMAXPROCS、负载条件、采集窗口,以及是否开启其他诊断。修改后应回到原来的业务指标比较;profile 里某个函数占比降低,可能只是别的成本上升,并不自动表示延迟改善。

block 与 mutex profile 还需要配置相应采集率,默认不能假设已经收集到足够样本。block 主要把同步阻塞归到等待位置,mutex 则有不同的竞争归因语义;两张排名不能当成同一种时间直接相加。采集开销也没有对所有程序通用的固定比例,应先在受控环境评估。

怎样避免拿到文件却下错结论?

不要用很短且没有目标请求的采集窗口否定一个偶发问题,也不要把不同版本二进制的符号解释混在一起。trace 文件可能很大,业务标记和诊断材料还可能携带内部名称,应按受控数据管理。本文示例只离线生成文件,不要求公开 pprof HTTP 端口。

pprof 是不是只能看 CPU?

不是。内存、goroutine、block 和 mutex 等材料分别回答不同问题。选择 pprof 后还必须选对 profile 类型与样本口径。

trace 看到 goroutine 在等,能直接改锁吗?

不能。等待可能是正常背压,也可能与网络、调度或工作分配有关。先把等待位置对应到业务负载,再决定是否需要 block、mutex profile 或外部依赖指标补充。

要不要一开始把两种工具全开?

通常先按问题取得最小必要材料,再补另一种视角。若必须同时采集,应把额外干扰计入解释,不能直接拿它与未开诊断的结果比较后归因。

参考:Go Diagnostics;runtime/pprof;runtime/trace;测试采集参数。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>