嵌入式C/C++软件开发&测试过程中,经常遇到某个模块软件运行耗时长,导致影响用户使用,或使业务性能指标劣化情况等。这个时候,我们就需要特别关注程序的性能。如何才能更好地优化程序性能呢?首先我们必须找到性能瓶颈点。在linux系统平台上,为了找到关键路径,我们可以利用profilng技术,使用gprof或oprofile工具。
gprof是GNU binutils工具之一,默认情况下linux系统当中都带有这个工具。在编译的时候,在每个用户态函数的出入口加入profiling代码,即在每个函数中都加入了一个名为mcount(or "_mcount" , or "__mcount" , 依赖于编译器或操作系统)的函数。当应用程序运行时,每一个函数都会调用mcount,而mcount会在内存中保存一张函数调用图,并通过函数调用堆栈的形式查找子函数和父函数的地址。这张调用图也保存了所有与函数相关的执行时间,调用次数,调用关系等信息,简单易懂。
gprof适合于查找用户态程序的性能瓶颈,对于很长时间都运行在内核态的程序,gprof不适合。
假设C文件test.c内容如下:
#include
void func_print(void)
{
printf("123\b\b\b");
return;
}
int func_loop(int x, int y)
{
int i = 0;
int j = 0;
for (i = 0; i < x; i++)
{
for (j = 0; j < y; j++)
{
func_print();
printf("abc\b\b\b");
}
}
return x * y;
}
void my_print(int x, int y)
{
printf("[gprof]: x=%d, y=%d\n",x, y);
return;
}
void main(int argc, char*argv[])
{
int i = 0;
const int x = 10;
const int y = 20000;
for (i = 0; i < 2; i++)
{
func_loop(x, y);
}
my_print(x, y);
return;
}
编译test.c时,需要增加-pg编译与链接选项,若要得到带注释的源码清单,则同时增加-g选项。执行编译,生成可执行程序test:
[root@HLZ gprof]# gcc -pg-g -o test test.c
[root@HLZ gprof]# ls
test test.c
运行test,程序退出之后,会在该进程执行目录下产生gmon.out文件:
[root@HLZ gprof]# ./test
[gprof]: x=10, y=20000
[root@HLZ gprof]# ls
gmon.out test test.c
注:当前目录下若已有gmon.out文件,则其内容将被本次运行生成的统计信息覆盖。多次运行同一程序时,需将前一次的gmon.out改名。
使用gprof来分析gmon.out文件时,需要把它和产生它的应用程序关联起来。通过gprof -b <进程文件名>gmon.out获取调用关系与执行时间信息:
[root@HLZ gprof]# gprof -b test gmon.out
Flat profile:
Each sample counts as 0.01seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
100.00 0.01 0.01 2 5.00 5.00 func_loop
0.00 0.01 0.00 400000 0.00 0.00 func_print
0.00 0.01 0.00 1 0.00 0.00 my_print
Call graph
granularity: each sample hitcovers 4 byte(s) for 100.00% of 0.01 seconds
index % time self children called name
0.01 0.00 2/2 main [2]
[1] 100.0 0.01 0.00 2 func_loop [1]
0.00 0.00 400000/400000 func_print [3]
-----------------------------------------------
[2] 100.0 0.00 0.01 main [2]
0.01 0.00 2/2 func_loop [1]
0.00 0.00 1/1 my_print [4]
-----------------------------------------------
0.00 0.00 400000/400000 func_loop [1]
[3] 0.0 0.00 0.00 400000 func_print [3]
-----------------------------------------------
0.00 0.00 1/1 main [2]
[4] 0.0 0.00 0.00 1 my_print [4]
-----------------------------------------------
Index by function name
[1] func_loop [3] func_print [4] my_print
显然地,上半部分是函数调用次数与调用时间统计信息,下半部分是函数调用关系信息。
特别说明几个gprof常用的分析选项[读者可根据需要使用,本文不再演示]:
1) -m num或--min-count=num:不显示被调用次数小于num的函数;
2) -z或--display-unused-functions:显示没有被调用的函数(按照调用计数和累积时间计算)。
3)-b:不再输出统计图表中每个字段的详细描述。
4)-p:只输出函数的调用图(Call graph的那部分信息)。
5)-q:只输出函数的时间消耗列表。
6)-e
7)-E
8)-f
9)-F
10)-A:输出一个带注释的“源代码清单”,它会注释源码,指出每个函数的执行次数(需要在编译时加-g选项)。
* gprof更多选项,读者可以在linux的shell下执行gprof--help查看。
可以通过结合gprof2dot.py和dot工具,实现gprof分析结果的图形化输出。
一个Python语言工具脚本,用于gmon.out生成dot文件。该脚本可以直接下载使用。如果想要显示全部的函数调用,可以 gprof2dot -n0 -e0,默认是n0.5,即影响小于5%的函数就不显示了。当然,这样图片会很乱,因为显示内容太多,可以 gprof2dot -n0 -e0 -s #-s选项表示不显示诸如模板,函数入口参数等等。
dot工具通过安装graphviz获得,具体方法:
tar -zxvf graphviz-2.38.0.tar.gz -C .
cd graphviz-2.38.0
./configure
make
make install
图形化gprof分析结果的语法:
gprof <进程文件>
图形格式也可以是png等格式。
[root@HLZ gprof]# gprof ./test ./gmon.out | ./gprof2dot.py -n0 -e0 | dot -Tjpg -o test.jpg
1) 程序必须正常退出(调用exit或从main中返回)才能生成统计信息。
如果你的程序是一个不会退出的服务程序,那就只有修改代码来达到目的。如果不想改变程序的运行方式,可以添加一个信号处理函数解决问题。可以采用注册信号,如在SIGUSR1信号处理函数中调用exit方法,来解决程序正常不会退出问题。
当使用kill -USR1
2) 调试多线程程序只能统计主线程的信息,即gmon.out只会生成进程主线程的函数调用信息;
3) 对于多进程,可以通过设置export GMON_OUT_PREFIX=gmon.out,使得进程退出后能够生成gmon.out.pid,避免文件同名而覆盖问题。
4) 使用ld链接时,需要用/lib/gcrt0.o代替crt0.o作为第一个input文件。
5) gprof只查看用户函数信息,不能对库函数进行查看。如果要调试libc库,链接时需要使用-lc_p代替-lc。