golang延时_使用pprof和gotorch排查golang的性能问题

最近线上服务压力很大,api的p99有点扛不住。广告业务对延时的要求普遍比较严格,有些adx设置的超时时间低至100ms,因此亟需找出性能热点。根据对目前系统情况的估计(和metrics埋点数据),大致估计问题出在广告的正排环节。使用 pprof  也证明了这一块确实是热点:

$ go tool pprof http://$IP:$PORT/debug/pprof/profile...(pprof) top 10Showing nodes accounting for 25.50s, 32.63% of 78.14s totalDropped 1533 nodes (cum <= 0.39s)Showing top 10 nodes out of 284      flat  flat%  sum%        cum  cum%4.56s  5.84%  5.84%      4.87s  6.23%  syscall.Syscall4.03s  5.16% 10.99%      4.03s  5.16%  runtime.aeshashbody3.50s  4.48% 15.47%      6.01s  7.69%  git.xxx.org/xxx/target.NewFilter2.78s  3.56% 19.03%      3.73s  4.77%  runtime.mapaccess2_fast642.63s  3.37% 22.40%      4.52s  5.78%  runtime.mapiternext2.08s  2.66% 25.06%      2.16s  2.76%  runtime.heapBitsForObject1.65s  2.11% 27.17%      1.93s  2.47%  runtime.mapaccess1_fast641.57s  2.01% 29.18%      2.96s  3.79%  runtime.mapaccess21.43s  1.83% 31.01%      2.06s  2.64%  runtime.runqgrab1.27s  1.63% 32.63%      1.27s  1.63%  runtime.epollwait(pprof) pngGenerating report in profile001.png (使用生成的线框图查看耗时)

其中第三行 NewFilter 就是正排过滤函数。因为一些历史原因,系统里不是所有定向条件都使用了倒排,正排实现起来毕竟简单、容易理解,而一旦开了这个口子,就会有越来越多正排加进来,推测是这个原因导致了性能的逐渐衰退。经过讨论,D同学花了一周多的时间,逐个梳理重写。在Libra(字节跳动内部的ABTest平台,参考谷歌分层实验框架方案)上开实验,平均耗时 -9%,从统计数据上来说,实验组比对照组有了显著的改进,但从最终结果上,整体的p95、p99超时都出现了进一步恶化。这说明真正的问题不在于正排的计算,优化的思路出现了偏差。考虑到晚高峰期间的cpu占用率也只是刚超过50%,也就是说有可能性能问题在于锁,但pprof的 block 和 mutex 都是空的,没有线索。猜测问题有可能在日志,代码里确实用得不少。日志用的是 github.com/ngaut/logging 库,每一次调用都会用到两个全局mutex。但通过调整log level 为error级别,大幅减少了日志量,并没有看到性能的改善。经过搜索,发现 uber 基于 pprof 开发了一个神器 go-torch,可以生成火焰图。安装好 go-torch 及依赖 FlameGraph 以后执行:

$ go-torch  -u http://$IP:$PORT -f cpu.svgINFO[14:52:23] Run pprof command: go tool pprof -raw -seconds 30 http://$IP:$PORT/debug/pprof/profileINFO[14:52:54] Writing svg to cpu.svg

用 Chrome 打开 cpu.svg,人肉排查:

5c9aace2b3e7635b1065f3e680ce16dc.png

可以看到,在NewFilter旁边竟然还有一个耗时接近的 runtime.growslice ,结合实际代码(略作简化),可以推测是 slice 的初始化长度不足。

matchAds := make([]*ad, 0, 4096)adsBitMap.GetList(func(seq int) {if NewFilter(ctx, ad) {        matchAds = append(matchAds, adlist[seq])    }})// 顺便提一下,bitmap是一个uint64数组,GetList(f) 是将每一个等于1的bit索引传给 f// GetList方法里面用了cpu的BSF指令来提高性能。

实际上最终定向后得到的广告往往在数万甚至数十万的级别,而 go 的 slice 扩容在超过1024个元素以后是1.25倍,可想而知会出现大量的内存分配和拷贝,导致性能随着广告数量的增加逐渐恶化。最近的广告数量也确实有了大幅的上升 —— 逻辑上形成了闭环。
经过优化,使用更大的初始化长度,并且使用 sync.Pool 来进一步减少内存分配,最终上线后p95和p99都下降了超过50%,效果显著。
参考文章:

golang 使用pprof和go-torch做性能分析 

https://www.cnblogs.com/li-peng/p/9391543.html


推荐阅读

  • 高手们都在使用的性能排查利器:白话火焰图

学习交流 Go 语言,扫码回复「进群」即可

f6ac97c23105b9124a60e63fb1067b26.png

站长 polarisxu

自己的原创文章

不限于 Go 技术

职场和创业经验

Go语言中文网

每天为你

分享 Go 知识

Go爱好者值得关注

4fd674265d30929f7ff774cbb8fbef6d.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值