如何利用 Golang 性能分析工具 pprof 排查 CPU 使用率异常问题?
- 内容介绍
- 文章标签
- 相关推荐
本文共计735个文字,预计阅读时间需要3分钟。
火图分析中,main.yourComputeFunc 占比超过80%,同时几乎不出现 runtime.futex、net/http.readRequest、runtime.gopark 和 gcMarkWorker。若这些runtime函数频繁出现,则说明不是计算瓶颈,而是调度阻塞或GC抢占。
- 用
GODEBUG=gctrace=1启动程序,看日志是否密集打印gc 123 @45.67s 0%:;有就先调GOGC或检查对象逃逸 - 别只看 top10,进交互后输
top -cum查调用链顶端,确认是不是上层循环反复触发了本不慢的函数 - 如果
time.Now()或log.Printf在火焰图里铺满扁平分支,删日志或换zap.Sugar()+ 条件包裹,别优化业务逻辑
采集 CPU profile 时最容易踩的三个坑
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30 看似简单,但错一步就白忙:
- 采样时间太短(如
?seconds=5)会漏掉周期性热点;太长(>60 秒)混入空闲噪声,30 秒是线上服务较稳妥的起点 -
?seconds=30是阻塞式请求,HTTP 连接会卡住 30 秒才返回文件——别误判为接口挂了 - 本地测试若程序秒退,改用
runtime/pprof.StartCPUProfile(f)手动控制启停,比 HTTP 方式更可靠
为什么 top 里全是 runtime.futex,却不是锁的问题?
runtime.futex 或 runtime.mcall 占比高,大概率不是你代码里那把 sync.RWMutex 写得太差,而是 Goroutine 调度太密集:
- 几千个 goroutine 卡在
chan receive上,等待永远不来的数据 - 大量 goroutine 反复抢同一把写锁,导致调度器不断切出/切入
- 这时该盯的是
/debug/pprof/goroutine?debug=2,看阻塞点分布,而不是翻 lock 相关代码
list 定位到某行后,下一步该查什么?
输入 list processItem 看到某行 json.Marshal 被高频采样,别急着换库——先确认它是否在 hot path 循环里被反复调用:
立即学习“go语言免费学习笔记(深入)”;
- 检查是否每次循环都 new 一个 struct 或 map,导致反射开销+内存分配双高
- 看是否能提前序列化并缓存,或实现
json.Marshaler接口绕过反射 - 注意
strings.Builder.Write或bytes.Buffer.Write如果排在-alloc_spacetop 前三,说明拼接逻辑在持续 new 底层切片
runtime.gopark 占 40% 时,能不能立刻想到去查 /debug/pprof/block,而不是盯着 handler 函数改逻辑。本文共计735个文字,预计阅读时间需要3分钟。
火图分析中,main.yourComputeFunc 占比超过80%,同时几乎不出现 runtime.futex、net/http.readRequest、runtime.gopark 和 gcMarkWorker。若这些runtime函数频繁出现,则说明不是计算瓶颈,而是调度阻塞或GC抢占。
- 用
GODEBUG=gctrace=1启动程序,看日志是否密集打印gc 123 @45.67s 0%:;有就先调GOGC或检查对象逃逸 - 别只看 top10,进交互后输
top -cum查调用链顶端,确认是不是上层循环反复触发了本不慢的函数 - 如果
time.Now()或log.Printf在火焰图里铺满扁平分支,删日志或换zap.Sugar()+ 条件包裹,别优化业务逻辑
采集 CPU profile 时最容易踩的三个坑
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30 看似简单,但错一步就白忙:
- 采样时间太短(如
?seconds=5)会漏掉周期性热点;太长(>60 秒)混入空闲噪声,30 秒是线上服务较稳妥的起点 -
?seconds=30是阻塞式请求,HTTP 连接会卡住 30 秒才返回文件——别误判为接口挂了 - 本地测试若程序秒退,改用
runtime/pprof.StartCPUProfile(f)手动控制启停,比 HTTP 方式更可靠
为什么 top 里全是 runtime.futex,却不是锁的问题?
runtime.futex 或 runtime.mcall 占比高,大概率不是你代码里那把 sync.RWMutex 写得太差,而是 Goroutine 调度太密集:
- 几千个 goroutine 卡在
chan receive上,等待永远不来的数据 - 大量 goroutine 反复抢同一把写锁,导致调度器不断切出/切入
- 这时该盯的是
/debug/pprof/goroutine?debug=2,看阻塞点分布,而不是翻 lock 相关代码
list 定位到某行后,下一步该查什么?
输入 list processItem 看到某行 json.Marshal 被高频采样,别急着换库——先确认它是否在 hot path 循环里被反复调用:
立即学习“go语言免费学习笔记(深入)”;
- 检查是否每次循环都 new 一个 struct 或 map,导致反射开销+内存分配双高
- 看是否能提前序列化并缓存,或实现
json.Marshaler接口绕过反射 - 注意
strings.Builder.Write或bytes.Buffer.Write如果排在-alloc_spacetop 前三,说明拼接逻辑在持续 new 底层切片
runtime.gopark 占 40% 时,能不能立刻想到去查 /debug/pprof/block,而不是盯着 handler 函数改逻辑。
