干掉性能大BUG 后台函数级问题定位介绍
valgrind是运行在linux上一套基于仿真技巧的程序调试和辨析工具。灵便轻便而又壮大,能直穿程序过失的中心,可谓是程序员的瑞士军刀。
首先,本文列举两种典型问题的定位思路,一种是cpu使用率过高问题,另一种是内存泄漏问题。
然后做下简单抽象,总结函数级性能问题定位的一般方法。
1. pve战斗,zonesvr的cpu使用率异常过高
Ø bug描述:
卡牌游戏ML,PVE战斗中,zonesvr的cpu使用率异常高,成为后台性能的瓶颈点。 后定位发现,原来是zonesvr里面的loadTeamCard和InitPlayerCard模块导致cpu使 用率过高,而这两个模块在gamesvr中同样存在,即同一处理过程,在两个不同的进程 走了两遍。
Ø 优化措施:
去掉zonesvr里面重复的代码片段,loadTeamCard和InitPlayerCard模块。以上两个模 块放在gamesvr里。
Ø 优化效果:
优化后,PVE战斗的tps(trans per second,服务器每秒处理的事务数)从1000/s提升 到2000/s。优化效果明显。
那么这个bug是如何定位出来的呢?首先,我们来看下PVE的战斗时序图。
根据时序图,zonesvr只是做转发请求的操作。真正的计算环节是在gamesvr,但在实际的性能测试中,发现zonesvr首先到了瓶颈,并且此时的gamesvr的cpu资源使用率很低,只有30%,PVE战斗的tps也很低,只有1000/s。
于是,感觉zonesvr应该是出了问题,拉起神器valgrind来定位cpu热点函数。
具体如何拉起,如何down下数据,km上面有其他文章,这里不再多言。
拿到valgrind的cpu数据,使用kcachegrind可视化查看结果,如图:
Incl和Self的区别是:
Incl表示inclusive,包含其调用的函数的消耗cost。
Self表示exclusive,具有排他性,只表示自身的消耗cost。
根据图中的数据(Incl主力,Self辅助),从顶部到底部的函数依次排查,发现红框内的函数消耗cost超出了预期。于是点击查看对应的调用关系图:
原来是这里出了问题。
把这个问题反馈给开发,原来这里这些步骤(红圈内)都是可以不用的。
因为在gamesvr中,也有同样的处理逻辑了。同一流程步骤,gamesvr和zonesvr都做 了一般,重复了。原来gamesvr是从zonesvr里剥离出来的,所以有一些代码还没优化。
至此,函数级问题定位完成。
大体思路是:理清测试场景的时序图,找到重点关注的svr进程,使用valgrind工具收集svr进程的函数热点数据,从顶到下,依次对每个函数进行数据分析,分析方法是基于incl与self的消耗情况。最后,得出结论。
2. login登录场景,infosvr内存泄漏
Ø bug描述:
infosvr底层使用了TC组件。在外面有一个ProcessSync是后来新增的,这里调用了 DBGet函数,DBGet里面申请了内存,但使用完内存后,ProcessSync忘了free。 导致出现leak。
Ø 优化措施:
在使用完DBGet内存后,做free操作。
If (NULL!=pszVal){free(pszVal);pszVal==NULL;}
Ø 优化效果:
优化后,无内存泄漏发生。
那么这个问题是如何发现的呢?
登录时序
Infosvr的作用:存储玩家数据information。底层使用tc(Tokyo Cabinet)的实现,infosvr只是在原来的基础上,加了一层壳而已。
压测过程中,发现Infosvr的资源使用情况有异常,内存使用如下:
观察到infosvr的常驻内存一直增涨,so,怀疑发生leak。启用神器valgrind收集leak数据。
这里简单说下几种内存泄漏的分类:
1) definitely lost: 明确地已经发生泄漏了,你需要修复这里。
2) indirectly lost: 说明内存泄漏发生在基于指针(pointer-based)的数据结构里。如 果修复了definitely lost,那么indirectly lost也应该会消失。
3) 可能内存泄漏,这是由于C/C++语言指针处理的特点造成的,这部分可能不太 准确。
4) still reachable: 表示该内存在程序运行完的时候,仍旧有指针指向它。
5) suppressed,表明这个leak error已经被取消了,你可以忽略掉它。
所以,我们需要重点关注definitely lost。
在项目中,或多或少会用到公司内外的一些库,对于这些库,valgrind也会采集到信息。但是在分析过程中,我们就可以把它们忽略掉了。
最后,经过一番排查,发现如下问题:
这里是说,有155,218个块,共11,019,820 bytes发生definitely lost。接下来,就可以根据函数栈的信息,进行问题定位了。
首先找到栈顶对应的DBGet代码:
可以看到,ppvValBuff申请了内存(malloc),但在该函数中并没有释放掉,所以外层的函数应该有释放才行。
又调出processSync函数代码:
ProcessSync函数调用DBGet申请了一块内存,但使用后,没有把对应内存free掉,从而导致memory leak。
在return前增加
If (NULL!=pszVal){free(pszVal);pszVal==NULL;}
搞定!至此,内存泄漏问题定位完成。
定位内存泄漏的大体思路:压测过程中,发现infosvr进程的内存使用异常,使用valgrind memcheck工具收集infosvr进程的内存泄漏信息,分析数据主要关注definitely lost部分,并且屏蔽使用的库函数。发现内存泄漏后,依次排查函数栈中的各函数,最后找到问题。
3. 函数级性能问题定位总结
3.1 了解系统
首先,对系统要有一定的了解,包括整体的架构,流程时序,计算过程。
其次,对系统进行分析,可能出现问题的点,不可能出现问题的点,并做好记录。
3.2 关注被测系统的性能情况
在压测过程中,对于被测系统的表现情况需要关注,一系列命令可以帮助查看系统表现,比如,top,Vmstat ,netstat,mpstat,sar等。发现可疑点,追查到底。
3.3 工欲善其事,必先利其器
函数级性能定位工具,不单单只有valgrind,还有perf,gprof等。
perl文章参考
http://km.oa.com/group/799/articles/show/99048
gprof 用户手册网站
http://sourceware.org/binutils/docs-2.17/gprof/index.html