程序分析是以某种语言书写的程序为对象,对其内部的运作流程进行分析。程序分析的目的主要有三点:一是通过程序内部各个模块之间的调用关系,整体上把握程序的运行流程,从而更好地理解程序,从中汲取有价值的内容。二是以系统优化为目的,通过对程序中关键函数的跟踪或者运行时信息的统计,找到系统性能的瓶颈,从而采取进一步行动对程序进行优化。最后一点,程序分析也有可能用于系统测试和程序调试中。当系统跟踪起来比较复杂,而某个BUG又比较难找时,可以通过一些特殊的数据构造一个测试用例,然后将分析到的函数调用关系和运行时实际的函数调用关系进行对比,从而找出错误代码的位置。
程序分析工具不同于调试器,它只产生程序运行时某些函数的调用次数、执行时间等等宏观信息,而不是每条语句执行时的详细信息。Gprof是Linux下一个强有力的程序分析工具。对于C、Pascal或者Fortran77语言的程序,它能够以“日志”的形式记录程序运行时的统计信息:程序运行中各个函数消耗的时间和函数调用关系,以及每个函数被调用的次数等等。从而可以帮助程序员找出众多函数中耗时最多的函数,也可以帮助程序员分析程序的运行流程。相信这些功能对于分析开源代码的程序员来说,有着相当大的诱惑力。
用gprof对程序进行分析主要分以下三个步骤:
l 用编译器对程序进行编译,加上-pg参数。
l 运行编译后的程序。
l 用gprof命令查看程序的运行时信息。
先以一个简单的例子演示一下吧。随便找一个能够运行的程序的源代码,比如下面的文件test.c:
首先,用以下命令进行编译:
[root@localhost]#gcc –otest –pg test.c
然后,运行可执行文件test.
[root@localhost]#./test
运行后,在当前目录下将生成一个文件gmon.out,这就是gprof生成的文件,保存有程序运行期间函数调用等信息。
最后,用gprof命令查看gmon.out保存的信息:
[root@localhost]#gproftest 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服务端编程,性能总是不可避免要思考的问题。
而单机(严格的说是单核)单线程程序(严格的说是逻辑)又是所有复杂应用的基础。所以,这块的性能是整个应用的基础。
当遇到应用相应很慢的时候我们往往会疑问:这么强劲的CPU到底在干什么,反应这么慢。
满足你!linux下常用的性能工具就是跟gcc一起的gprof。来个例子程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
#include <stdio.h>
#include <stdlib.h>
void
f1() {
int
i;
int
*p;
for
(i = 0; i < 10; i++) {
p =
malloc
(
sizeof
(
int
));
*p = 10;
free
(p);
}
}
void
f2() {
int
i;
int
*p;
for
(i = 0; i < 20; i++) {
p =
malloc
(
sizeof
(
int
));
*p = 10;
free
(p);
}
}
void
f3() {
int
i;
int
*p;
for
(i = 0; i < 30; i++) {
p =
malloc
(
sizeof
(
int
));
*p = 10;
free
(p);
}
}
int
main() {
int
i;
for
(i = 0; i < 1000000; i++) {
f1();
f2();
f3();
}
return
0;
}
|
哈哈,好烂的程序啊。我们现在要通过gprof找出这个程序运行时cpu都用来干什么了。
要启用gprof很简单,gcc编译的时候带上-pg参数即可:
1
|
gcc -g -pg test.c -o test
|
下面运行./test。运行完我们可以看到目录下多了个gmon.out的文件。这就是gprof的日志,里面记录了程序运行cpu的使用信息。打开看看?杯具,二进制文件,我们人类看不懂。。。我们要运行下面的命令生成报表:
1
|
gprof ./test gmon.out >report.txt
|
打开report.txt,我们可以看到两张表。
第一张:
1
2
3
4
5
6
7
8
|
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ns/call ns/call name
56.25 0.32 0.32 1000000 315.00 315.00 f3
33.04 0.50 0.18 1000000 185.00 185.00 f2
10.71 0.56 0.06 1000000 60.00 60.00 f1
|
这就是每个函数占用cpu的时间以及百分比了。我们可以很明显的看到f1()、f2()和f3()所用的时间关系。很准确。
第二张表式函数调用表,描述了函数调用的相互关系:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
granularity: each sample hit covers 4 byte(s) for 1.79% of 0.56 seconds
index % time self children called name
<spontaneous>
[1] 100.0 0.00 0.56 main [1]
0.32 0.00 1000000/1000000 f3 [2]
0.18 0.00 1000000/1000000 f2 [3]
0.06 0.00 1000000/1000000 f1 [4]
-----------------------------------------------
0.32 0.00 1000000/1000000 main [1]
[2] 56.2 0.32 0.00 1000000 f3 [2]
-----------------------------------------------
0.18 0.00 1000000/1000000 main [1]
[3] 33.0 0.18 0.00 1000000 f2 [3]
-----------------------------------------------
0.06 0.00 1000000/1000000 main [1]
[4] 10.7 0.06 0.00 1000000 f1 [4]
-------------------------------
|
接着上面的report.txt,执行下面命令:
1
2
3
|
gprof2dot report.txt > test.dot
dot -Tpng -o test.png
|
第一句的意思是将报表转化为dot文件(graphviz http://www.graphviz.org/图像文件格式)。第二句的意思是将这个文件再转为png格式。好吧现在用图像软件打开吧:
不用解释了吧。调用次数/本身所花cpu时间/调用的函数所花时间 一目了然!