程序分析是以某种语言书写的程序为对象,对其内部的运作流程进行分析。程序分析的目的主要有三点:一是通过程序内部各个模块之间的调用关系,整体上把握程序的运行流程,从而更好地理解程序,从中汲取有价值的内容。二是以系统优化为目的,通过对程序中关键函数的跟踪或者运行时信息的统计,找到系统性能的瓶颈,从而采取进一步行动对程序进行优化。最后一点,程序分析也有可能用于系统测试和程序调试中。当系统跟踪起来比较复杂,而某个BUG又比较难找时,可以通过一些特殊的数据构造一个测试用例,然后将分析到的函数调用关系和运行时实际的函数调用关系进行对比,从而找出错误代码的位置。
程序分析工具不同于调试器,它只产生程序运行时某些函数的调用次数、执行时间等等宏观信息,而不是每条语句执行时的详细信息。Gprof是Linux下一个强有力的程序分析工具。对于C、Pascal或者Fortran77语言的程序,它能够以“日志”的形式记录程序运行时的统计信息:程序运行中各个函数消耗的时间和函数调用关系,以及每个函数被调用的次数等等。从而可以帮助程序员找出众多函数中耗时最多的函数,也可以帮助程序员分析程序的运行流程。相信这些功能对于分析开源代码的程序员来说,有着相当大的诱惑力。
用gprof分析程序
用gprof对程序进行分析主要分以下三个步骤:
l 用编译器对程序进行编译,加上-pg参数。
l 运行编译后的程序。
l 用gprof命令查看程序的运行时信息。
先以一个简单的例子演示一下吧。随便找一个能够运行的程序的源代码,比如下面的文件test.c:
1
2 int IsEven(int x)
3
4 {
5
6 return 0 == x & 1;
7
8 }
9
10 int main(int argc, char *argv[]
11
12 {
13
14 int i = 0;
15
16 while(++i < 1000) IsEven(i);
17
18 }
首先,用以下命令进行编译:
[root@localhost]#gcc –o test –pg test.c
然后,运行可执行文件test.
[root@localhost]#./test
运行后,在当前目录下将生成一个文件gmon.out,这就是gprof生成的文件,保存有程序运行期间函数调用等信息。
最后,用gprof命令查看gmon.out保存的信息:
[root@localhost]#gprof test gmon.out –b
这样就有一大堆信息输出到屏幕上,有函数执行单间,函数调用关系图等等,如下:
Flat profile:
Each sample counts as 0.01 seconds.
no time accumulated
% cumulative self self total
time seconds seconds calls Ts/call Ts/call name
0.00 0.00 0.00 1000 0.00 0.00 IsEven(int)
Call graph
granularity: each sample hit covers 2 byte(s) no time propagated
index % time self children called name
0.00 0.00 1000/1000 main [7]
[8] 0.0 0.00 0.00 1000 IsEven(int) [8]
-----------------------------------------------
Index by function name
[8] IsEven(int)
以上介绍了gprof最简单的使用方法,下面针对其使用过程中的三个步骤详细说明。
编译和链接
上面的例子中,程序比较简单,只有一个文件。如果源代码有多个文件,或者代码结构比较复杂,编译过程中先生成若干个目标文件,然后又由链接器将这些目标文件链接到一起,这时该怎么使用gprof呢?
对于由多个源文件组成的程序,编译时需要在生成每个.o文件的时候加上-pg参数,同时在链接的时候也要加上-pg参数。对于链接器不是GCC的情况,如ld,又有特殊的要求。
同时,-pg参数只能记录源代码中各个函数的调用关系,而不能记录库函数的调用情况。要想记录每个库函数的调用情况,链接的时候必须指定库函数的动态(或者静态)链接库libc_p.a,即加上-lc_p,而不是-lc。
还要说明的是,如果有一部分代码在编译时指定了-pg参数,而另一部分代码没有指定,则生成的gmon.out文件中将缺少一部分函数,也没有那些函数的调用关系。但是并不影响gprof对其它函数进行记录。
运行
编译好的程序运行时和运行一般的程序没有什么不同,只是比正常的程序多生成了一个文件gmon.out。注意,这个文件名是固定的,没法通过参数的设置进行改变。如果程序目录中已经有一个gmon.out,则它会被新的gmon.out覆盖掉。
关于生成的gmon.out文件所在的目录,也有以下约定:程序退出时所运行的文件所在目录就是生成的gmon.out文件所在的目录。如果一个程序执行过程中调用了另一个程序,并在另一个程序的运行中终止,则gmon.out会在另一个程序所在的目录中生成。
还有一点要注意的就是当程序非正常终止时不会生成gmon.out文件,也因此就没法查看程序运行时的信息。只有当程序从main函数中正常退出,或者通过系统调用exit()函数而退出时,才会生成gmon.out文件。而通过底层调用如_exit()等退出时不会生成gmon.out。
查看
查看程序运行信息的命令是gprof,它以gmon.out文件作为输入,也就是将gmon.out文件翻译成可读的形式展现给用户。其命令格式如下:
gprof [可执行文件] [gmon.out文件] [其它参数]
方括号中的内容可以省略。如果省略了“可执行文件”,gprof会在当前目录下搜索a.out文件作为可执行文件,而如果省略了gmon.out文件,gprof也会在当前目录下寻找gmon.out。其它参数可以控制gprof输出内容的格式等信息。最常用的参数如下:
l -b 不再输出统计图表中每个字段的详细描述。
l -p 只输出函数的调用图(Call graph的那部分信息)。
l -q 只输出函数的时间消耗列表。
l -e Name 不再输出函数Name 及其子函数的调用图(除非它们有未被限制的其它父函数)。可以给定多个 -e 标志。一个 -e 标志只能指定一个函数。
l -E Name 不再输出函数Name 及其子函数的调用图,此标志类似于 -e 标志,但它在总时间和百分比时间的计算中排除了由函数Name 及其子函数所用的时间。
l -f Name 输出函数Name 及其子函数的调用图。可以指定多个 -f 标志。一个 -f 标志只能指定一个函数。
l -F Name 输出函数Name 及其子函数的调用图,它类似于 -f 标志,但它在总时间和百分比时间计算中仅使用所打印的例程的时间。可以指定多个 -F 标志。一个 -F 标志只能指定一个函数。-F 标志覆盖 -E 标志。
l -z 显示使用次数为零的例程(按照调用计数和累积时间计算)。
不过,gprof不能显示对象之间的继承关系,这也是它的弱点.
linux环境下 C++性能测试工具 gprof + kprof + gprof2dot
1.gprof
很有名了,google下很多教程
g++ -pg -g -o test test.cc
./test //会生成gmon.out
gprof ./test > prof.log
看一下对于我前面提到的huffman编码压缩+解码解压缩全部过程的一个程序
对于生成的prof.log ,wow, 很有用处的但是看起来有点累,不是吗:)
Code
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls s/call s/call name
32.51 1.71 1.71 13127166 0.00 0.00 glzip::HuffTree<unsigned char, glzip::decode_hufftree>::decode_byte(unsigned char, glzip::Buffer&, glzip::HuffNode<unsigned char>*&, int)
18.73 2.69 0.98 105017328 0.00 0.00 glzip::Buffer::write_bit(int)
16.63 3.57 0.88 24292128 0.00 0.00 glzip::Buffer::write_string(std::string const&)
7.22 3.95 0.38 61711820 0.00 0.00 glzip::Buffer::read_byte(unsigned char&)
5.80 4.25 0.30 37419691 0.00 0.00 glzip::Buffer::write_byte(unsigned char)
5.51 4.54 0.29 1 0.29 2.48 glzip::Encoder<unsigned char>::do_encode_file(glzip::char_tag)
5.13 4.82 0.27 105017721 0.00 0.00 glzip::HuffNode<unsigned char>::is_leaf()
3.04 4.97 0.16 1 0.16 0.31 glzip::Encoder<unsigned char>::do_caculate_frequency(glzip::char_tag)
2.47 5.11 0.13 1 0.13 2.42 glzip::HuffTree<unsigned char, glzip::decode_hufftree>::decode_file()
1.14 5.17 0.06 24292227 0.00 0.00 std::vector<std::string, std::allocator<std::string> >::operator[](unsigned int)
0.95 5.21 0.05 1 0.05 0.05 glzip::HuffTree<unsigned char, glzip::encode_hufftree>::do_serialize_tree(glzip::HuffNode<unsigned char>*, glzip::Buffer&)
0.29 5.23 0.01 24292133 0.00 0.00 glzip::HuffTreeBase<unsigned char>::root() const
0.29 5.25 0.01 574 0.00 0.00 glzip::Buffer::flush_buf()
0.19 5.25 0.01 6 0.00 0.00 glzip::Buffer::~Buffer()
0.10 5.26 0.01 unsigned int const& std::min<unsigned int>(unsigned int const&, unsigned int const&)
0.00 5.26 0.00 12901 0.00 0.00 std::_Deque_iterator<glzip::HuffNode<unsigned char>*, glzip::HuffNode<unsigned char>*&, glzip::HuffNode<unsigned char>**>::_Deque_iterator(std::_Deque_iterator<glzip::HuffNode<unsigned char>*, glzip::HuffNode<unsigned char>*&, glzip::HuffNode<unsigned char>**> const&)
0.00 5.26 0.00 6065 0.00 0.00 std::__deque_buf_size(unsigned int)
0.00 5.26 0.00 6056 0.00 0.00 std::_Deque_iterator<glzip::HuffNode<unsigned char>*, glzip::HuffNode<unsigned char>*&, glzip::HuffNode<unsigned char>**>::_S_buffer_size()
0.00 5.26 0.00 5656 0.00 0.00 std::_Deque_iterator<glzip::HuffNode<unsigned char>*, glzip::HuffNode<unsigned char>*&, glzip::HuffNode<unsigned char>**>::operator*() const
0.00 5.26 0.00 5656 0.00 0.00 std::_Deque_iterator<glzip::HuffNode<unsigned char>*, glzip::HuffNode<unsigned char>*&, glzip::HuffNode<unsigned char>**>::operator+=(int)
0.00 5.26 0.00 4871 0.00 0.00 std::_Deque_iterator<glzip::HuffNode<unsigned char>*, glzip::HuffNode<unsigned char>*&, glzip::HuffNode<unsigned char>**>::operator+(int) const
0.00 5.26 0.00 3126 0.00 0.00 glzip::HuffNode<unsigned char>::weight() const
0.00 5.26 0.00 1465 0.00 0.00 glzip::HuffTree<unsigned char, glzip::encode_hufftree>::HuffNodePtrGreater::operator()(glzip::HuffNode<unsigned char> const*, glzip::HuffNode<unsigned char> const*)
0.00 5.26 0.00 946 0.00 0.00 glzip::Buffer::fill_buf()
0.00 5.26 0.00 785 0.00 0.00 std::_Deque_iterator<glzip::HuffNode<unsigned char>*, glzip::HuffNode<unsigned char>*&, glzip::HuffNode<unsigned char>**>::operator-(int) const
0.00 5.26 0.00 785 0.00 0.00 std::_Deque_iterator<glzip::HuffNode<unsigned char>*, glzip::HuffNode<unsigned char>*&, glzip::HuffNode<unsigned char>**>::operator-=(int)
0.00 5.26 0.00 590 0.00 0.00 glzip::HuffNode<unsigned char>::left() const
0.00 5.26 0.00 590 0.00 0.00 glzip::HuffNode<unsigned char>::right() const
0.00 5.26 0.00 453 0.00 0.00 operator new(unsigned int, void*)
.
2.Kprof
sudo apt-get install kprof
kprof -f ./test //注意已经按照前面的用gprof生成 gmont.out 了。
将gprof , GUI化了,更加友好,还是很方便的,它也利用而来graphviz绘制了运行时流程图,但是对于大的程序,
似乎效果不太好,流程图感觉完全乱掉了,而且对于大程序,kprof运行的有点慢,在我的破机器上跑半天才出来。
总的来说它的object profile还是很方便有用的。
object profile 示意图
graph view 示意图,这个仅作参考吧,同样是用graphviz怎么和gprof2dot差距这么大呢?:)对比下下面gprof2dot的生成图吧!
3.gprof2dot.py
这个脚本太TMD的牛X了,速度快效果好,怪不得底下评论一片叫好,这就是life_saving的工具啊!
首先就是运行时的程序流程图,网上有很多工具有些就是利用gprof的,有很多文章介绍,都不用看了,就用这个吧。
然后很直观的看出每个步骤的占用时间百分比,函数调用次数,颜色能直观的表示出瓶颈所在。
gprof2dot安装看这里吧
http://www.51testing.com/?uid-13997-action-viewspace-itemid-79952
gprof ./test | gprof2dot.py | xdot
恩 去下载那个xdot.py吧 看dot文件,太方便了,不需要先生成图像了。
直接看效果图吧!
1.整体图
清晰多了吧,上面那么多冗余的东西。
2.局部图
和我的程序执行过程完全对应,清晰明了,太赞了!
每个方框内部显示函数名称,函数整体包括内部子函数占用时间%比,函数自身,不包括内部子函数占用时间%比,函数执行次数。
边表示父函数调用该子函数占用的时间%比,调用次数。
之所以清晰是因为,gprof2dot默认是部分函数调用图,对性能影响不大的函数调用都不显示,例如上图中没有出现类的构造,析构函数,
如果想要显示全部的函数调用,可以 gprof2dot -n0 -e0 ,默认是n0.5即影响小于5%的函数就不显示了。
当然这样图片会很乱,因为显示内容很多,可以 gprof2dot -n0 -e0 -s #-s表示不显示诸如模板,函数入口参数等等,使得
函数名称显示更加精简。
恩,如果用户能决定只输出某个函数及其关联函数的显示信息图就更好了,例如上面有两个
独立的过程compress()和decompress()如果用户能够决定只是显示compress()就好了,
不过考虑到模板之类的稍微有点麻烦,但是应该也是能实现的!
4. google 还有一个开源的能测试工具
以后尝试一下
http://code.google.com/p/google-perftools/wiki/GooglePerformanceTools
http://blog.csdn.NET/stanjiang2010/article/details/5655143