第13章 软件剖析和度量
1,软件剖析的目标是找出程序执行时最消耗时间的代码段。
gprof介绍
gprof是一个GNU profiler工具。可以显示程序运行的“flat profile”,包括每个函数的调用次数,每个函
数消耗的处理器时间,也可以显示“调用图”,包括函数的调用关系,每个函数调用花费了多少时间。还可
以 显示“注释的源代码”--是程序源代码的一个复本,标记有程序中每行代码的执行次数。
基本用法:
1. 使用-pg选项编译和链接你的应用程序。
2. 执行你的应用程序,使之运行完成后生成供gprof分析的数据文件(默认是gmon.out)。
3. 使用gprof程序分析你的应用程序生成的数据,例如:gporf a.out gmon.out。
gprof 实现原理:
gprof并不神奇,在编译和链接程序的时 候(使用 -pg 编译和链接选项),gcc 在你应用程序的每个函数中
都加入了一个名为mcount(or“_mcount”, or“__mcount”)的函数,也就是说-pg编译的应用程序里的每
一个函数都会调用mcount, 而mcount会在内存中保存一张函数调用图,并通过函数调用堆栈的形式查找子函
数和父函数的地址。这张调用图也保存了所有与函数相关的调用时间,调用次 数等等的所有信息。
1. 在内存中分配一些内存,存储程序执行期间的统计数据
2. 在GCC使用-pg选项编译后,gcc会在程序的入口处(main 函数之前)调用
void monstartup(lowpc, highpc)
在每个函数的入口处调用
void _mcount()
在程序退出时(在 atexit () 里)调用
void _mcleanup()
monstartup:负责初始化profile环境,分配内存空间
_mcount: 记录每个函数代码的caller和callee的位置
_mcleanup:清除profile环境,保存结果数据为gmon.out,供gprof分析结果
3.在_mcount函数中跟踪程序的执行状况,记录程序代码的执行次数,时间等数据。
常用的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文件。
【命令】time — 执行命令并计时
【格式】time [-p] command [arguments...]
【说明】
执行命令行"command [arguments...]",命令行执行结束时在标准输出中打印执行该命令行的时间统计结果
,其统计结果包含以下数据:
1)实际时间(real time): 从command命令行开始执行到运行终止的消逝时间;
2)用户CPU时间(user CPU time): 命令执行完成花费的用户CPU时间,即命令在用户态中执行时间总和;
3)系统CPU时间(system CPU time): 命令执行完成花费的系统CPU时间,即命令在核心态中执行时间总和。
其中,用户CPU时间和系统CPU时间之和为CPU时间,即命令占用CPU执行的时间总和。实际时间要大于CPU时间
,因为Linux是多任务操作系统,往往在执行一条命令时,系统还要处理其它任务。
另一个需要注意的问题是即使每次执行相同命令,但所花费的时间也是不一样,其花费时间是与系统运行相
关的。
例1:
1. # time date
2. Sun Mar 26 22:45:34 GMT-8 2006
3.
4. real 0m0.136s
5. user 0m0.010s
6. sys 0m0.070s
7. #
在例1中,执行命令"time date"(见第1行)。系统先执行命令"date",第2行为命令"date"的执行结果。第3-6
行为执行命令"date"的时间统计结果,其中第4行"real"为实际时间,第5行"user"为用户CPU时间,第6
行"sys"为系统CPU时间。以上三种时间的显示格式均为MMmNN[.FFF]s。
在例1中,CPU时间 = 用户CPU时间 系统CPU时间 = 0m0.010s 0m0.070s = 0m0.080s,实际时间大于CPU时间
,说明在date命令运行的同时,还有其它任务在运行。
【参数说明】
-p 以POSIX缺省的时间格式打印时间统计结果,单位为秒。详细的输出格式见例2。
例2:
1. # time -p date
2. Wed Mar 27 00:33:11 GMT-8 2006
3. real 0.11
4. user 0.00
5. sys 0.02
6. #
在例2中,同样执行命令"time date"(见第1行)。系统先执行命令 "date",第2行为该命令的执行结果。第3
-5行为执行命令"date"的时间统计结果。注意本例的时间格式与例1中的时间格式差别,使用-p 参数后的时
间显示格式为NN.FF,其单位为秒。