性能分析
目录
google-perftools
介绍
性能分析通过抽样方法完成,默认是 1 秒 100 个样本,一个样本是 10 毫秒,即时间单位是 10 毫秒;可以通过环境变量 CPUPROFILE_FREQUENCY 设置采样频率。
CPU profiler 是基于采样工作的。所以采样次数影响着性能报告的准确性。如果采样次数过少,则你会发现同样的程序同样的数据,每次输出的性能报告中的热点都不一样。
使用方法
直接调用 API
在代码中调用 ProfilerStart
和 ProfilerStop
这两个函数。
链接静态库
在代码 link 过程中添加参数 –lprofiler
gcc […] -o helloworld –lprofiler
运行程序: CPUPROFILE=./helloworld.prof ./helloworld
指定要 profile 的程序为 helloworld,并且指定产生的分析结果文件的路径为./helloworld.prof
链接动态库
这种方式和静态库的方式差不多,但不用重新编译或重新链接,所以使用非常方便。只需要在运行时添加 LD_PRELOAD 变量。
LD_PRELOAD="/usr/lib/libprofiler.so" CPUPROFILE=./helloworld.prof ./helloworld
使用信号触发 profile
手动设置信号
由于我们的程序有可能是服务程序,而服务程序不会自动执行完退出,如果以 ctrl+c
退出也不是正常的 exit(0)
的方式退出,而这会导致我们在 profile 的时候,收集到的数据不全甚至是空的,采用如下解决办法:
将 ProfilerStart
和 ProfilerStop
这 2 个函数封装到两个信号处理函数中,给服务程序发信号 SIGUSR1,就开始 profile,给服务程序发信号 SIGUSR2,就停止 profile。这样我们可以随时对程序进行 profiling,并获得数据。代码如下:
#include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <signal.h> #include <google/profiler.h> //SIGUSR1: start profiling //SIGUSR2: stop profiling static void gprof_callback(int signum) { if (signum == SIGUSR1) { printf("Catch the signal ProfilerStart\n"); ProfilerStart("bs.prof"); } else if (signum == SIGUSR2) { printf("Catch the signal ProfilerStop\n"); ProfilerStop(); } } static void setup_signal() { struct sigaction profstat; profstat.sa_handler = gprof_callback; profstat.sa_flags = 0; sigemptyset(&profstat.sa_mask); sigaddset(&profstat.sa_mask, SIGUSR1); sigaddset(&profstat.sa_mask, SIGUSR2); if ( sigaction(SIGUSR1, &profstat,NULL) < 0 ) { fprintf(stderr, "Fail to connect signal SIGUSR1 with start profiling"); } if ( sigaction(SIGUSR2, &profstat,NULL) < 0 ) { fprintf(stderr, "Fail to connect signal SIGUSR2 with stop profiling"); } } int main(int argc,char** argv) { // // ... // setup_signal(); // // ... // return 0; }
启动程序后,可以采用 kill -s SIGUSR1 <pid> 和 kill -s SIGUSR2 <pid> 来开始采样和停止采样。
使用 CPUPROFILESIGNAL 设置信号
设置的信号必须是正常情况下未被程序使用的。
CPUPROFILE=./helloworld.prof CPUPROFILESIGNAL=12 ./helloworld
可以使用 killall -12 hellworld
触发 profile 开始,然后过一段时间后再次使用 killall -12 hellworld
触发 profile 结束并生成文件。
数据分析
生成数据
- 生成文字数据
google-pprof <bin path> <profile path> --text
- 生成网页数据
google-pprof <bin path> <profile path> --web
数据含义
每一列的数据解释:
- 该函数中的采样数量
- 该函数中的采样百分比
- 到目前为止打印的函数中的采样百分比
- 该函数和被其调用的函数中的采样数量
- 该函数和被其调用的函数中的采样百分比
gprof
介绍
grof 可以获取 C 程序运行期间的统计数据,如每个函数的耗时,调用次数及各个函数的调用关系,(gprof 统计的是 CPU 占用时间,I/O 时间不计算在内。通常 gprof 的采样周期是 0.01s,统计项越接近这个值误差可能越大。若函数的运行时间低于 0.01S,统计值会显示为 0。
类似于 gdb,gprof 需要对待分析的程序做一些改动,因此在程序编译的时候需要加上"-pg"选项,如果程序的某个模块在编译的时候没有加上"-pg",则该模块的函数会被排除在统计范围之外。比如想要查看库函数的 profiling,则需在链接库函数的时候用“-lc_p"代替”-lc"(gprof 是各个类 UNIX 的标准工具,系统自带的链接库通常有两个版本,它们的区别在于编译的时候是否加上了"-pg"。用-lc_p 等于告诉编译器选择加上了"-pg"的那个版本)。 加上"-pg"选项后,程序的入口会于 main()之前调用 monstartup(),主要是申请内存存储接下来获取的统计信息。
在每个函数中会调用_mcount(),主要是在函数的堆栈中查询父函数和子函数的地址并保存下来。最后会在程序退出前调用_mcleanup(),将统计结果保存到 gmon.out 中,并完成清除工作。
gprof 统计各个函数的运行时间是采用的抽样的方法,周期性的查看 Programcounter 指向哪一个函数的地址段,并把结果以直方图的形式保存下来。
使用方法
- 编译时添加选项
-pg
- 运行程序,生成 gmonf.out 文件
- 使用 gprof 进行分析,
gprof <bin path> <gmonf path>
数据分析
Flat Profile 示例图
标注 | 释义 |
---|---|
%time | 每个函数占用的时间比例,所有函数占比和为 100% |
cumulative seconds | 函数及其调用函数执行累计占用时间 |
self seconds | 单独函数执行累计占用时间 |
calls | 函数调用次数 |
self ms/call | 每次调用函数花费的时间,单位毫秒, 不包含调用函数运行的时间 |
total ms/call | 每次调用函数花费的时间,单位毫秒,包括调用函数运行的时间 |
name | 函数名称 |
Call Graph 示例图
标注 | 释义 |
---|---|
index | 每个函数第一次出现时分配一个编号,根据编号可以方便查找函数的具体分析数据 |
%time | 函数以及调用子函数所占用的总运行时间的百分比 |
self | 函数的总运行时间 |
children | 子函数执行的总时间 |
called | 函数被调用的次数,不包括递归调用 |
name | 函数名称, name 列中,可查看函数之间的调用关系 |