linux下的程序分析工具——gprof

GNU gprof 是一款linux平台上的程序分析软件(unix也有prof)。借助gprof可以获得C程序运行期间的统计数据,例如每个函数耗费的时间,函数被调用的次数以及各个函数相互之间的调用关系。gprof可以帮助我们找到程序运行的瓶颈,对占据大量CPU时间的函数进行调优(gprof统计的只是CPU的占用时间,对I/O瓶颈貌似无能为力,耗时甚久的I/O操作很可能只占据极少的CPU时间)。
gprof的使用非常简单,在编译链接的时候加上"-pg"选项,然后按照正常方式运行程序,如果程序正常退出,一个名为gmon.out将会产生。使用gprof可查看gmon.out中的统计结果:

gprof <options> [executable-file] [profile-data-file(s)……] [>outfile]

可通过man gprof 查看各选项含义,通常会加上"-b"选项禁止显示冗长的说明信息。
executable-file如果没有指定,则会默认为a.out。
profile-data-file可跟多个文件,若没有指定,默认gmon.out。
统计信息较多,最好重定向到outfile方便查看。
最终呈现的统计信息包括两张表:flat table和call graph。flat table列出了各个函数的运行时间(不包括子函数)及所占总运行时间的比率,函数的调用次数;call graph还包括函数之间的调用关系,详细列出了每个函数在它的各个子函数上所耗费的时间。
下面一个简单的例子说明一下两张表中各项的含义:
待分析的程序源码:
#define MAX 10000000
void f() {
long long sum = 0;
for (long long i=0;i<MAX;i++)
sum += i;
}
void g() {
long long sum = 0;
for (long long i=0;i<MAX;i++)
sum += i;
f();
}
int main() {
long long sum = 0;
for (long long i=0;i<MAX;i++)
sum += i;
f();
g();
}

Flat profile:
Each sample counts as 0.01 seconds.
%          cumulative         self        self                          total        
time        seconds        seconds   calls        ms/call     ms/call   name   
50.00        0.07                0.07        2            35.00      35.00        f()
28.57        0.11                0.04        1            40.00      75.00        g()
21.43        0.14                0.03                                                        main
% time:各个函数占用的时间比率(不包括子函数),这一列加起来应该为100%
cumulative seconds:累积时间,当前行减去上一行即为当前函数耗费时间
self seconds:当前函数耗费时间(不包括子函数)
self calls:调用次数
ms/call:调用一次耗费的平均时间(不包括子函数),单位毫秒
total ms/call:同上,但包括子函数
name:函数名

call graph:
granularity: each sample hit covers 4 byte(s) for 7.14% of 0.14 seconds
index      % time    self   children    called        name
                                                                   <spontaneous>
[1]            100.0   0.03     0.11                        main [1]
                           0.04      0.04       1/1          g() [2]
                           0.04      0.00       1/2          f() [3]
-----------------------------------------------
                           0.04      0.04         1/1          main [1]
[2]              53.6    0.04      0.04          1            g() [2]
                           0.04      0.00         1/2          f() [3]
-----------------------------------------------
                           0.04      0.00         1/2          g() [2]
                           0.04      0.00         1/2          main [1]
[3]              50.0    0.07      0.00          2            f() [3]
-----------------------------------------------

每个函数都分配了一个index,index按升序排列,一个函数对应一个entry,两个entry之间用虚线隔开。
在每个entry中,以[index]起头的行称为primary line。primary line上面的行称为caller's line,列举的是调用该函数的函数;下面的行subroutine's line列举的是该函数调用的子函数。这三种line的各项名称虽然相同,但有着截然不同的含义。
以下都以第二个entry为例说明:

primary line
index      % time    self    children    called       name
[2]              53.6    0.04      0.04         1            g() [2]
%time:g()耗费的时间比率。该比率包括了调用的f(),因此各个entry的该项数字加起来不等于100%。
self:同flat table的self seconds。
children:f()耗费的时间。下面的subroutines's line 的self项和children项之和应等于该数值。
called:只被调用了一次。

subroutine's line
index      % time      self      children      called        name
                              0.04      0.00            1/2         f() [3]
self:f()被g()调用过程中,f()的耗费时间0.04s。
children:f()被g()调用过程中,f()中的子函数耗费时间为0。
called:f()一共被调用了2次,其中有1次被g()调用。

caller's line
index      % time      self      children      called        name
                              0.04      0.04            1/1           main [1]
self:g()被main()调用过程中,g()的耗费时间。
children:g()被main()调用过程中,g()中的子函数耗费的时间。
called:g()一共被调用了1次,其中1次是被main()调用的。

gprof的基本原理
类似于gdb,gprof需要对待分析的程序做一些改动,因此在程序编译的时候需要加上"-pg"选项,如果程序的某个模块在编译的时候没有加上"-pg",则该模块的函数会被排除在统计范围之外。比如想要查看库函数的profiling,则需在链接库函数的时候用“-lc_p"代替”-lc"(gprof是各个类UNIX的标准工具,系统自带的链接库通常有两个版本,它们的区别在于编译的时候是否加上了"-pg"。用-lc_p等于告诉编译器选择加上了"-pg"的那个版本)。
加上"-pg"选项后,程序的入口会于main()之前调用monstartup(),主要是申请内存存储接下来获取的统计信息。
在每个函数中会调用mcount(),主要是在函数的堆栈中查询父函数和子函数的地址并保存下来。
最后会在程序退出前调用_mcleanup(),将统计结果保存到gmon.out中,并完成清除工作。
gprof统计各个函数的运行时间是采用的抽样的方法,周期性的查看Program counter指向哪一个函数的地址段,并把结果以直方图的形式保存下来。
 

常用的gprof命令选项:

-b 不再输出统计图表中每个字段的详细描述。
-p
只输出函数的调用图(Call graph的那部分信息)。
-q
只输出函数的时间消耗列表。
-e Name
不再输出函数Name及其子函数的调用图(除非它们有未被限制的其它父函数)。可以给定多个-e标志。一个 -e 标志只能指定一个函数。
-E Name
不再输出函数Name及其子函数的调用图,此标志类似于-e 标志,但它在总时间和百分比时间的计算中排除了由函数Name及其子函数所用的时间。
-f Name
输出函数Name及其子函数的调用图。可以指定多个-f 标志。一个-f 标志只能指定一个函数
-F Name
输出函数Name及其子函数的调用图,它类似于-f 标志,但它在总时间和百分比时间计算中仅使用所打印的例程的时间。可以指定多个-F 标志。一个-F 标志只能指定一个函数。-F标志覆盖 -E 标志。
-z
显示使用次数为零的例程(按照调用计数和累积时间计算)。

使用注意:
1
一般gprof只能查看用户函数信息。如果想查看库函数的信息,需要在编译是再加入“-lc_p”编译参数代替“-lc”编译参数,这样程序会链接libc_p.a库,才可以产生库函数的profiling信息
2
gprof只能在程序正常结束退出之后才能生成程序测评报告,原因是gprof通过在atexit()里注册了一个函数来产生结果信息,任何非正常退出都不会执行atexit()的动作,所以不会产生gmon.out文件。如果你的程序是一个不会退出的服务程序,那就只有修改代码来达到目的。如果不想改变程序的运行方式,可以添加一个信号处理函数解决问题(这样对代码修改最少),例如:

static void sighandler( int sig_no )
{
exit(0);
}
signal( SIGUSR1, sighandler )

当使用kill -USR1 pid后,程序退出,生成gmon.out文件。
编辑其他 C/C++程序分析器
还有其他很多分析器可以使用gprof的数据, 例如 KProf (截屏)cgprof虽然图形界面的看起来更舒服,但我个人认为命令行的gprof使用更方便

PS:
有人建议在编译时不要加上"-g"选项,因为这样可能会影响分析结果。
通常gprof的采样周期是0.01s,统计项越接近这个值误差可能越大。若函数的运行时间低于0.01S,统计值会显示为0。

关于这一主题的有用链接:
http://www.cs.utah.edu/dept/old/texinfo/as/gprof_toc.html :关于options, flat table, call graph有更详细的论述。
http://wiki.waterlin.org/Cpp/gprof.html :对gprof的实现原理论述得很清晰。
http://sam.zoy.org/writings/programming/gprof.html :对多线程profiling。

你可能感兴趣的:(linux下的程序分析工具——gprof)