用 net/http/pprof 快速获取 CPU profile 数据,需导入 _ "net/http/pprof" 并启动 HTTP server(如 http.ListenAndServe(":6060", nil)),访问 /debug/pprof/ 确认生效后,执行 go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30 采集;务必使用与 profile 对应的原始未重编译二进制文件解析,否则函数名显示为 ???;采样须在真实流量下进行,避免空闲或初始化阶段,且生产环境慎用超 60 秒采样。

怎么用 net/http/pprof 快速拿到 CPU profile 数据
直接在服务启动前加一行 import _ "net/http/pprof",再起个 HTTP server(哪怕只监听本地),就能通过 HTTP 接口按需采集——这是最轻量、最贴近真实场景的方式。
- 必须确保路由注册成功:只要
http.ListenAndServe(":6060", nil)启动了,/debug/pprof/就自动可用;访问http://localhost:6060/debug/pprof/能看到列表才算生效 - 采集命令别漏参数:
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30——seconds=30是显式指定时长,不加会默认 30 秒,但写明更稳妥 - 别在没流量时采样:CPU profile 捕获的是「正在执行指令」的栈,空闲服务采出来全是
runtime.futex或runtime.mcall,看不出业务热点 - 生产环境慎用 >60 秒:采样本身有 5–10% 额外 CPU 开销,长时间开启可能扰动正常请求处理
为什么 go tool pprof 打开后显示 no samples
90% 是因为没传原始可执行文件路径,pprof 解析不了符号——它不是纯数据解析器,而是需要二进制文件来映射函数名和行号。
- 错误写法:
go tool pprof cpu.pprof→ 缺失二进制,函数全显示为???或地址 - 正确写法:
go tool pprof ./myserver cpu.pprof→./myserver必须是生成该 profile 的**同一份未重编译的二进制** - 交叉编译或 Docker 部署时特别容易翻车:本地编译的 profile 不能用 CI 构建的二进制分析;容器内采集的 profile 也不能拿宿主机二进制去解析
- 如果用
go run main.go启动,profile 无法关联到源码行——必须用go build出可执行文件再运行
top 和 web 看到的耗时数字到底代表什么
flat 是函数自身执行时间(不含子调用),sum 是包含所有子调用的总耗时。优化优先盯 flat 高但逻辑可简化的函数,比如反复拼接字符串、没缓存的 JSON 序列化。
-
top10输出里某函数flat占比 45%,但sum只有 47%,说明它自身开销大,子调用很轻——重点看它内部循环或计算逻辑 -
web生成火焰图后,顶部宽条是瓶颈入口;若某 handler 下层层展开都窄,但中间一个strings.ReplaceAll特别宽,就说明这里是热点,不是调用链问题 -
list 函数名能看到每行代码的采样次数,但注意:Go 编译器可能内联或重排,行号未必完全精确,仅作参考 - 如果
runtime.mallocgc或runtime.scanobject占比异常高,实际是内存分配压力大,该切到allocsprofile 查对象来源,不是 CPU 问题
什么时候不该用 HTTP 方式,而该用 runtime/pprof 写文件
命令行工具、离线任务、或 HTTP 接口不可达(如无网络容器、权限受限环境)时,必须用 runtime/pprof 手动控制采样生命周期。
立即学习“go语言免费学习笔记(深入)”;
- 关键点:必须
defer pprof.StopCPUProfile(),且要在main()返回前触发;用os.Interrupt信号监听比单纯defer更可靠 - 文件路径要可写:
os.Create("/tmp/cpu.prof")比os.Create("cpu.prof")更少因工作目录问题失败 - 测试中直接生成:
go test -cpuprofile=cpu.test.prof ./...,适合复现稳定性能问题,不用起服务 - 别在 goroutine 里启停:
pprof.StartCPUProfile是进程级的,多 goroutine 同时调用不会报错但行为未定义,统一由 main 控制











