最近都在做服务器端的性能测试, 主要在做profile, 业界用的最多的估计就是万能的valgrind, 我们的引擎还嵌入了google的google-perftools. 具体的测试数据会在这周完成, 这里只记录下我对这两个工具的原理理解
Valgrind
这东东是linux 上很出名的开源性能测试工具, 主要的功能是测试内存, 还提供了cpu cache命中, call 统计, heap 测试, 线程测试等功能, 灰常强大, 所以应用面很广.
valgrind 现在已经可以算是庞然大物了, 手册都有300页, 代码没仔细看, 只简单了解了下它的原理.
valgrind 的执行不需要重新编译, 只要把二进制作为参数传入即可. 运行时会先把binary的debug 信息(估计主要是符号表)都读到自己的内存中, 用于结果的输出, 否则看到一堆地址, 估计谁都抓狂. 然后在内存中根据当前运行环境为找到对应的tools , 并根据tools 参数选择交给哪个继续执行, 默认是memcheck. 而最关键的是valgrind 的core, 程序的所有cpu指令都将被core接管, 再真正被执行. tools 会对指令进行处理, 插入自己需要的指令后, 再交给cpu, 以达到测试的目的.
由于接管了所有cpu指令, 所以valgrind 不但能测试到自有代码, 还会接管系统库的代码. 不过这也是比较头疼的, 因为输出的结果会包含很多我不关心的数据, 还好的是valgrind 提供方法去过滤这些信息. 但由于插入的代码量是根据工具不同不一样, 而且所有调用以及内存访问都可能会被处理, 所以程序会变的很慢, 官方数据是10-50倍的减速… 所以每次valgrind 都是很折磨的, 特别是我们这样的即时战斗游戏, 技能会出现很诡异的效果. 所以用valgrind的测试也许不是非常适合我们游戏.
Google-perftools
这个最早是google内部用的性能优化工具, 最出名的是它提供的tcmalloc, 号称在多线程的情况下是ptmalloc的10倍性能提升, 峰值出现在16 threads, 分配256k字节数据的时候. 再往后性能差别不大(参考 tmalloc 性能测试报告). 但我们没有用到这个模块, 用的是它提供的cpu profiler.
google的cpu profiler (下简称pprof, 本来想用gprof, 但又和GNU的prof 名字冲突了, 就用了它提供的一个工具的名字)用的是取样的方法, 这个方法我曾经在stack overflow上看到一个老外说的(@maguschen 同学推荐的). 老外说的方法更原始, gdb 调试程序, 然后随意Ctrl+C, 对断点所在函数做个统计就可以知道程序的热点大概在什么地方了. 很有道理! goolgle 的这个profiler 完全就是根据这个原理实现的.
由于这个工具的代码不大(src/profiler.cc), 网上也有一些指引, 所以我是大概把原理过了一遍的, 大致如下:
- 必须把profiler的库连接到程序中. 因为pprof 的初始化是通过一个库中的static 变量来实现的.
- 在类的cstor中,会调用Start

- 其中重要的函数是StartTimer, 它启动了一个定时器,

- 还有一个重要的函数是EnableHandler, 它注册了SIGPROF, 下面是这两个函数的实现(src/profile-handler.cc ):

- 可以看到, 默认是启动了一个1000000 usec 的定时器, 定时器fire 的时候会发出SIGPROF 信号, pprof 响应了这个信号. 所以被测试的程序不能接管这个型号(这点是我的猜测, 没有通读代码, 不敢说这个一定成立, 但尽量不要这样做)
而下面这段就是pprof的精华所在了:
- 这里使用了libunwind库, 把现在的调用栈dump出来了, 然后放到collector中, 可以看到pprof 是异步输出的, 数据到将来的某个时刻才最后输出(一般是程序终结的时候, 也可用ProfileFlush函数把内容flush 到io去)
可以看到pprof 是基于采样, 和最上面提到的stack overflow上面提到的老外是一个方法, 所以pprof的结果是不准确的, 应该说是提供了一个统计学上的意义, 我们知道采样的可信度是和样本空间的大小是相关的, 所以如果想要pprof比较准确就需要较大的样本空间, 这可以通过2个方法做到:
- 增加测试时间;
- 增加采样密度;
而用pprof最大的感受是一个字 — "快", 和许多profile 工具不一样, pprof 是很快的, 基本上感觉不到性能的下降, 甚至可以在生产环境只用它.