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

jackxiang 2013-8-13 18:24 | |
这个更界面化:http://www.cnblogs.com/rocketfan/archive/2009/11/15/1603465.html

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指向哪一个函数的地址段,并把结果以直方图的形式保存下来。

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。
http://linuxfocus.org/English/March2005/article371.meta.shtml:提到了perl和java的profiling方法。

来自:http://blog.sina.com.cn/s/blog_6608391701013phr.html

作者:jackxiang@向东博客 专注WEB应用 构架之美 --- 构架之美,在于尽态极妍 | 应用之美,在于药到病除
地址:http://jackxiang.com/post/6581/
版权所有。转载时必须以链接形式注明作者和原始出处及本声明!


最后编辑: jackxiang 编辑于2013-8-13 18:28
评论列表
发表评论

昵称

网址

电邮

打开HTML 打开UBB 打开表情 隐藏 记住我 [登入] [注册]