gprof的使用介绍
转于:http://blog.chinaunix.net/uid-25194149-id-3215487.html
#不知道这是在哪里找的了,感谢各位~
性能分析工具gprof介绍
Ver:1.0
目录
1. GPROF介绍 4
2. 使用步骤 4
3. 使用举例 4
3.1 测试环境 4
3.2 测试代码 4
3.3 数据分析 5
3.3.1 flat profile模式 6
3.3.2 call graph模式 7
4. 链接库中的函数 7
5. 使用局限 8
6. 分析示例 12
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1. gprof介绍
gprof是GNU profile工具,可以运行于linux、AIX、Sun等操作系统进行C、C++、Pascal、Fortran程序的性能分析,用于程序的性能优化以及程序瓶颈问题的查找和解决。通过分
析应用程序运行时产生的“flat profile”,可以得到每个函数的调用次数,每个函数消耗的处理器时间,也可以得到函数的“调用关系图”,包括函数调用的层次关系,每个函
数调用花费了多少时间。
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2. 使用步骤
1) 用gcc、g++、xlC编译程序时,使用-pg参数,如:g++ -pg -o test.exe test.cpp
编译器会自动在目标代码中插入用于性能测试的代码片断,这些代码在程序运行时采集并记录函数的调用关系和调用次数,并记录函数自身执行时间和被调用函数的执行时间。
2) 执行编译后的可执行程序,如:./test.exe。该步骤运行程序的时间会稍慢于正常编译的可执行程序的运行时间。程序运行结束后,会在程序所在路径下生成一个缺省文
件名为gmon.out的文件,这个文件就是记录程序运行的性能、调用关系、调用次数等信息的数据文件。
3) 使用gprof命令来分析记录程序运行信息的gmon.out文件,如:gprof test.exe gmon.out则可以在显示器上看到函数调用相关的统计、分析信息。上述信息也可以采用
gprof test.exe gmon.out> gprofresult.txt重定向到文本文件以便于后续分析。
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3. 使用举例
3.1 测试环境
本文提供的样例的测试环境如下:
? Linux server164 2.6.9-22.ELsmp #1 SMP Mon Sep 19 18:32:14 EDT 2005 i686 i686 i386 GNU/Linux
? gcc version 3.2.3 20030502 (Red Hat Linux 3.2.3-47.3)
? GNU gprof 2.15.92.0.2
3.2 测试代码
清单 1. 耗时测试应用程序示例
example1.c
#include
int a(void)
{
int i=0,g=0;
while(i++<100000)
{
g+=i;
}
return g;
}
int b(void)
{
int i=0,g=0;
while(i++<400000)
{
g +=i;
}
return g;
}
int main(int argc, char** argv)
{
int iterations;
if(argc != 2)
{
printf("Usage %s \n", argv[0]);
exit(-1);
}
else
iterations = atoi(argv[1]);
printf("No of iterations = %d\n", iterations);
while(iterations--)
{
a();
b();
}
}
这个应用程序包括两个函数:a 和 b,它们通过运行不同次数的循环来消耗不同的CPU时间。main 函数中采用了一个循环来反复调用这两个函数。函数b中循环的次数是 a 函数的
4 倍,因此我们期望通过gprof的分析结果可以观察到大概有 20% 的时间花在了 a 函数中,而 80% 的时间花在了 b 函数中。
3.3 数据分析
在 gcc 编译命令中加上 –pg参数即可。编译方法如下:
gcc example1.c -pg -o example1 -O2 -lc
在编译好这个应用程序之后,按照普通方式运行这个程序:
./example1 50000
程序运行完之后,应该会看到在当前目录中新创建了一个文件 gmon.out。
3.3.1 flat profile模式
使用 gprof 命令分析gmon.out 文件,如下所示:
gprof example1 gmon.out -p
-p参数标识“flat profile”模式,在分析结果中不显示函数的调用关系,AIX平台默认此参数有效。
输出以下内容:
清单 2. flat profile 的结果
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
80.38 203.27 203.27 50000 4.07 4.07 b
19.61 252.87 49.60 50000 0.99 0.99 a
0.00 252.88 0.01 main
上面结果中各个列的含义如下:
%time 函数以及衍生函数(函数内部再次调用的子函数)所占的总运行时间的百分比
cumulative seconds 函数累计执行的时间
self seconds 函数执行占用的时间
calls 函数的调用次数
self ms/call 每一次调用函数花费的时间microseconds,不包括衍生函数的运行时间
total ms/call 每一次调用函数花费的时间microseconds,包括衍生函数的运行时间
name 函数名称
列的含义,在gprof的输出结果中都有详细的说明。
从输出结果中可以看到,正如我们期望的一样,b 函数所花费的时间大概是 a 函数所花费的时间的 4倍。
很多函数调用(例如 printf)在上面的输出结果中都没有出现。这是因为大部分函数都是在C链接库(libc.so)中,而链接库并没有使用 -pg 进行编译,因此就没有对链接库中
的函数收集调度信息。
3.3.2 call graph模式
如果希望反映函数之间的调用关系,需要采用如下命令:
gprof example1 gmon.out –q
-q参数标识“call graph”模式,在分析结果中显示函数的调用关系。
输出以下内容:
清单 3. Call graph
granularity: each sample hit covers 4 byte(s) for 0.00% of 252.72 seconds
index % time self children called name
[1] 100.0 0.00 252.72 main [1]
201.41 0.00 50000/50000 b [2]
51.31 0.00 50000/50000 a [3]
-----------------------------------------------
201.41 0.00 50000/50000 main [1]
[2] 79.7 201.41 0.00 50000 b [2]
-----------------------------------------------
51.31 0.00 50000/50000 main [1]
[3] 20.3 51.31 0.00 50000 a [3]
-----------------------------------------------
上面结果中各个列的含义如下:
index 每个函数第一次出现时都分配了一个编号,根据编号可以方便的查找函数的具体分析数据
%time 函数以及衍生函数(函数内部再次调用的子函数)所占的总运行时间的百分比
self 函数的总运行时间
children 衍生函数执行的总时间
called 函数被调用的次数,不包括递归调用
name 函数名称
在name列中,可以看出函数之间的调用关系,main函数调用a、b函数,b函数被main函数调用,a函数被main函数调用。通过函数名称后面的数字来标识这个文件中的函数,从而可
以快速定位函数的分析数据的位置,经过一层层的逐步深入的分析就得到各个函数的调用关系以及各个函数的性能数据。
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4. 链接库中的函数
正如在前面曾经介绍的,对于代码剖析的支持是由编译器增加的,因此如果希望从链接库(比如libc.a)中获得剖析信息,就需要使用 -pg 来编译这些库。很多操作系统默认提供
了已经启用代码剖析支持而编译的链接库版本,例如:libc.a对应的采用-pg编译的文件为libc_p.a。对于没有按照标准提供类似libc_p.a链接库的操作系统版本来说,就需要安装
已经编译好的启用代码剖析的链接库版本或者自己下载链接库的源代码进行编译。
使用“启用代码剖析的链接库版本”的应用场景,举例如下:
gcc example1.c -g -pg -o example1 -O2 -lc_p
然后,像普通情况下一样运行gprof对gmon.out进行分析,可以获得 flat profile 或 call graph,而此时的分析结果会非常丰富,包含很多C的标准库函数的调用信息,例如:
printf、write等。
注意:上例的libc_p.a是静态链接库。gprof目前还不支持对动态链接库中的函数进行性能分析。
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5. 使用局限
gprof的特点是它只能分析应用程序在运行过程中所消耗掉的CPU时间,只有当应用程序的函数消耗CPU的时候,gprof才能够获取函数的性能数据。如果应用程序在运行过程中暂时
挂起,并在系统内核唤醒应用程序后进一步执行,那么在应用程序中间暂停的时间性能数据是无法统计的;而且在应用程序等待I/O操作返回的时间,性能数据也是无法统计的。
如果对清单 1 稍加修改,就可以清楚地看出这个问题:
清单 5. 为清单 1 添加sleep()函数调用
example2.c:
#include
int a(void)
{
sleep(1); /*调用系统函数进行sleep*/
return 0;
}
int b(void)
{
sleep(4); /*调用系统函数进行sleep*/
return 0;
}
int main(int argc, char** argv)
{
int iterations;
if(argc != 2)
{
printf("Usage %s \n", argv[0]);
exit(-1);
}
else
iterations = atoi(argv[1]);
printf("No of iterations = %d\n", iterations);
while(iterations--)
{
a();
b();
}
}
现在 a 函数和 b 函数不再处理繁忙的循环了,而是分别调用sleep()来挂起1秒和4秒。
使用“启用代码剖析的链接库版本”,编译这个应用程序:
gcc example2.c -g -pg -o example2 -O2 -lc_p
并让这个应用程序循环 30 次:
./example2 30
执行gprof example2 gmon.out –p所生成的结果如下:
清单 6. flat profile 显示了系统调用的结果
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 21 0.00 0.00 _IO_file_overflow
0.00 0.00 0.00 13 0.00 0.00 strncmp
0.00 0.00 0.00 8 0.00 0.00 memset
0.00 0.00 0.00 8 0.00 0.00 sigprocmask
0.00 0.00 0.00 7 0.00 0.00 getenv
0.00 0.00 0.00 6 0.00 0.00 memcpy
0.00 0.00 0.00 5 0.00 0.00 _int_malloc
0.00 0.00 0.00 5 0.00 0.00 malloc
0.00 0.00 0.00 5 0.00 0.00 sigaction
0.00 0.00 0.00 5 0.00 0.00 strsep
0.00 0.00 0.00 4 0.00 0.00 nanosleep
0.00 0.00 0.00 4 0.00 0.00 sleep
0.00 0.00 0.00 4 0.00 0.00 strpbrk
0.00 0.00 0.00 3 0.00 0.00 _IO_new_file_xsputn
0.00 0.00 0.00 3 0.00 0.00 ____strtoul_l_internal
0.00 0.00 0.00 3 0.00 0.00 __cxa_atexit
0.00 0.00 0.00 3 0.00 0.00 __strtoul_internal
0.00 0.00 0.00 2 0.00 0.00 __errno_location
0.00 0.00 0.00 2 0.00 0.00 __find_specmb
0.00 0.00 0.00 2 0.00 0.00 a
0.00 0.00 0.00 2 0.00 0.00 b
0.00 0.00 0.00 2 0.00 0.00 mempcpy
0.00 0.00 0.00 1 0.00 0.00 _IO_default_xsputn
0.00 0.00 0.00 1 0.00 0.00 _IO_doallocbuf
0.00 0.00 0.00 1 0.00 0.00 _IO_file_doallocate
0.00 0.00 0.00 1 0.00 0.00 _IO_file_stat
0.00 0.00 0.00 1 0.00 0.00 _IO_file_write
0.00 0.00 0.00 1 0.00 0.00 _IO_setb
0.00 0.00 0.00 1 0.00 0.00 ____strtol_l_internal
0.00 0.00 0.00 1 0.00 0.00 ___fxstat64
0.00 0.00 0.00 1 0.00 0.00 __init_misc
0.00 0.00 0.00 1 0.00 0.00 __libc_csu_fini
0.00 0.00 0.00 1 0.00 0.00 __libc_csu_init
0.00 0.00 0.00 1 0.00 0.00 __libc_init_first
0.00 0.00 0.00 1 0.00 0.00 __libc_init_secure
0.00 0.00 0.00 1 0.00 0.00 __libc_setup_tls
0.00 0.00 0.00 1 0.00 0.00 __libc_start_main
0.00 0.00 0.00 1 0.00 0.00 __pthread_initialize_minimal
0.00 0.00 0.00 1 0.00 0.00 __setfpucw
0.00 0.00 0.00 1 0.00 0.00 __strtol_internal
0.00 0.00 0.00 1 0.00 0.00 _dl_aux_init
0.00 0.00 0.00 1 0.00 0.00 _dl_important_hwcaps
0.00 0.00 0.00 1 0.00 0.00 _dl_init_paths
0.00 0.00 0.00 1 0.00 0.00 _dl_non_dynamic_init
0.00 0.00 0.00 1 0.00 0.00 _itoa_word
0.00 0.00 0.00 1 0.00 0.00 _mcleanup
0.00 0.00 0.00 1 0.00 0.00 atexit
0.00 0.00 0.00 1 0.00 0.00 atoi
0.00 0.00 0.00 1 0.00 0.00 exit
0.00 0.00 0.00 1 0.00 0.00 fillin_rpath
0.00 0.00 0.00 1 0.00 0.00 fini
0.00 0.00 0.00 1 0.00 0.00 flockfile
0.00 0.00 0.00 1 0.00 0.00 funlockfile
0.00 0.00 0.00 1 0.00 0.00 main
0.00 0.00 0.00 1 0.00 0.00 mmap
0.00 0.00 0.00 1 0.00 0.00 printf
0.00 0.00 0.00 1 0.00 0.00 setitimer
0.00 0.00 0.00 1 0.00 0.00 strrchr
0.00 0.00 0.00 1 0.00 0.00 uname
0.00 0.00 0.00 1 0.00 0.00 vfprintf
0.00 0.00 0.00 1 0.00 0.00 write
对以上输出结果进行分析可见,尽管 profile 已经记录了每个函数被调用的确切次数,但是为这些函数记录的时间(实际上是所有函数)都是 0.00。这是因为 sleep 函数实际上
是执行了一次对内核空间的调用,从而将应用程序的执行挂起了,然后有效地暂停执行,并等待内核再次将其唤醒。由于花在用户空间执行的时间与花在内核中睡眠的时间相比非
常小,因此就被取整成零了。其原因是 gprof 以固定的周期对程序运行时间进行采样测量,当程序挂起时,gprof就不会对程序进行采样测量。
Gprof的特性使得有些程序非常难以进行优化,例如花费大部分时间睡眠等待内核唤醒的程序,或者由于外部因素(例如操作系统的 I/O 子系统过载)而运行得非常慢的程序。
通常,有一个很好的基准测试可以用来查看 gprof 对于应用程序的优化能起多大作用,方法是在 time 命令下面执行应用程序。此命令会显示一个应用程序运行完成需要多少时间
,并且在用户空间和内核空间各花费了多少时间。
例如:time ./example2 30
输出结果如下所示:
清单 7. time 命令的输出结果
No of iterations = 30
real 2m30.295s
user 0m0.000s
sys 0m0.004s
我们可以看出应用程序整体运行150秒左右,但大部分时间在睡眠状态,几乎没有多少时间被花费在执行用户空间和内核空间的代码上,此时gprof的分析结果无法体现函数的实际
运行时间。
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6. 分析示例
在新疆联通进行综合入库程序上线前的测试过程中,测试人员发现程序运行效率低下。下面以整个调优的过程来举例说明基于gprof的性能分析方法。
性能分析前的准备阶段,性能分析数据的生成步骤如下:
1) 首先更改makefile文件中的CC=g++ -g –pg 行,添加-pg参数。
2) 执行make clean删除所有的目标文件、可执行文件
3) 重新编译
4) 在命令行执行可执行程序,并通过控制台发送启动命令
5) 程序退出后在fileindb执行程序目录下生成gmon.out(不是控制台目录)
6) 执行gprof fileindb > gsmindb_mon_result.txt把分析结果重定向到gsmindb_mon_result.txt文件
下面就是从gsmindb_mon_result.txt文件中截取的部分内容:
打开gsmindb_mon_result.txt文件后,可以看到127行:
[3] 37.2 0.00 41.90 1 .main [3]
0.00 41.88 1/1 .CMainManager::Run(int&) [4]
可见main函数中的CMainManager::Run运行时间(41.88)几乎占据了所有的运行时间(41.90),再查CMainManager::Run的分析数据在148行:
[4] 37.2 0.00 41.88 1 .CMainManager::Run(int&) [4]
0.00 41.81 1/1 .CBaseBillFile::DoEveryBill() [5]
可见CMainManager::Run 函数中CBaseBillFile::DoEveryBill执行占了几乎所有的时间,对于占用时间比重很小的函数可以忽略。采用上面的方法一步一步向下查找,最终确定289
行:
0.56 11.98 849922/849922 .CMobileFavour::CheckAllCondition(int,int) [11]
[12] 11.1 0.56 11.98 849922 .CGsmFavourCheck::CheckAllCondition() [12]
0.06 5.82 849922/849922 .CGsmFavourCheck::CheckCallKind() [14]
在CGsmFavourCheck::CheckAllCondition()所调用的函数中CGsmFavourCheck::CheckCallKind()占用时间的比重过大,几乎达到50%;CGsmFavourCheck::CheckAllCondition总共用
时11.98,而CGsmFavourCheck::CheckCallKind()函数就占用了5.82,从而说明CGsmFavourCheck::CheckCallKind()函数需要作进一步优化,以提高整体的运行效率。
二:
这些天自己试着对项目作一些压力测试和性能优化,也对用过的测试工具作一些总结,并把相关的资料作一个汇总,以便以后信手拈来!
1 简介
改进应用程序的性能是一项非常耗时耗力的工作,但是究竟程序中是哪些函数消耗掉了大部分执行时间,这通常都不是非常明显的。GNU 编译器工具包所提供了一种剖析工具 GNU profiler(gprof)。gprof 可以为 Linux平台上的程序精确分析性能瓶颈。gprof精确地给出函数被调用的时间和次数,给出函数调用关系。
gprof 用户手册网站 http://sourceware.org/binutils/docs-2.17/gprof/index.html
2 功能
Gprof 是GNU gnu binutils工具之一,默认情况下linux系统当中都带有这个工具。
1. 可以显示“flat profile”,包括每个函数的调用次数,每个函数消耗的处理器时间,
2. 可以显示“Call graph”,包括函数的调用关系,每个函数调用花费了多少时间。
3. 可以显示“注释的源代码”--是程序源代码的一个复本,标记有程序中每行代码的执行次数。
3 原理
通过在编译和链接程序的时候(使用 -pg 编译和链接选项),gcc 在你应用程序的每个函数中都加入了一个名为mcount ( or “_mcount” , or “__mcount” , 依赖于编译器或操作系统)的函数,也就是说你的应用程序里的每一个函数都会调用mcount, 而mcount 会在内存中保存一张函数调用图,并通过函数调用堆栈的形式查找子函数和父函数的地址。这张调用图也保存了所有与函数相关的调用时间,调用次数等等的所有信息。
4 使用流程
1. 在编译和链接时 加上-pg选项。一般我们可以加在 makefile 中。
2. 执行编译的二进制程序。执行参数和方式同以前。
3. 在程序运行目录下 生成 gmon.out 文件。如果原来有gmon.out 文件,将会被重写。
4. 结束进程。这时 gmon.out 会再次被刷新。
5. 用 gprof 工具分析 gmon.out 文件。
5 参数说明
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 –b 二进制程序 gmon.out >report.txt
6 报告说明
Gprof 产生的信息解释:
%time |
Cumulative seconds |
Self Seconds |
Calls |
Self TS/call |
Total TS/call |
name |
该函数消耗时间占程序所有时间百分比 |
程序的累积执行时间 (只是包括gprof能够监控到的函数) |
该函数本身执行时间 (所有被调用次数的合共时间) |
函数被调用次数 |
函数平均执行时间 (不包括被调用时间) (函数的单次执行时间) |
函数平均执行时间 (包括被调用时间)
(函数的单次执行时间) |
函数名 |
Call Graph 的字段含义:
Index |
%time |
Self |
Children |
Called |
Name |
索引值 |
函数消耗时间占所有时间百分比 |
函数本身执行时间 |
执行子函数所用时间 |
被调用次数 |
函数名 |
注意:
程序的累积执行时间只是包括gprof能够监控到的函数。工作在内核态的函数和没有加-pg编译的第三方库函数是无法被gprof能够监控到的,(如sleep()等)
Gprof 的具体参数可以 通过 man gprof 查询。
7 共享库的支持
对于代码剖析的支持是由编译器增加的,因此如果希望从共享库中获得剖析信息,就需要使用 -pg 来编译这些库。提供已经启用代码剖析支持而编译的 C 库版本(libc_p.a)。
如果需要分析系统函数(如libc库),可以用 –lc_p替换-lc。这样程序会链接libc_p.so或libc_p.a。这非常重要,因为只有这样才能监控到底层的c库函数的执行时间,(例如memcpy(),memset(),sprintf()等)。
gcc example1.c –pg -lc_p -o example1
注意要用ldd ./example | grep libc来查看程序链接的是libc.so还是libc_p.so
8 用户时间与内核时间
gprof 的最大缺陷:它只能分析应用程序在运行过程中所消耗掉的用户时间,无法得到程序内核空间的运行时间。通常来说,应用程序在运行时既要花费一些时间来运行用户代码,也要花费一些时间来运行 “系统代码”,例如内核系统调用sleep()。
有一个方法可以查看应用程序的运行时间组成,在 time 命令下面执行程序。这个命令会显示一个应用程序的实际运行时间、用户空间运行时间、内核空间运行时间。
如 time ./program
输出:
real 2m30.295s
user 0m0.000s
sys 0m0.004s
9 注意事项
1. g++在编译和链接两个过程,都要使用-pg选项。
2. 只能使用静态连接libc库,否则在初始化*.so之前就调用profile代码会引起“segmentation fault”,解决办法是编译时加上-static-libgcc或-static。
3. 如果不用g++而使用ld直接链接程序,要加上链接文件/lib/gcrt0.o,如ld -o myprog /lib/gcrt0.o myprog.o utils.o -lc_p。也可能是gcrt1.o
4. 要监控到第三方库函数的执行时间,第三方库也必须是添加 –pg 选项编译的。
5. gprof只能分析应用程序所消耗掉的用户时间.
6. 程序不能以demon方式运行。否则采集不到时间。(可采集到调用次数)
7. 首先使用 time 来运行程序从而判断 gprof 是否能产生有用信息是个好方法。
8. 如果 gprof 不适合您的剖析需要,那么还有其他一些工具可以克服 gprof 部分缺陷,包括 OProfile 和 Sysprof。
9. gprof对于代码大部分是用户空间的CPU密集型的程序用处明显。对于大部分时间运行在内核空间或者由于外部因素(例如操作系统的 I/O 子系统过载)而运行得非常慢的程序难以进行优化。
10. gprof 不支持多线程应用,多线程下只能采集主线程性能数据。原因是gprof采用ITIMER_PROF信号,在多线程内只有主线程才能响应该信号。但是有一个简单的方法可以解决这一问题:http://sam.zoy.org/writings/programming/gprof.html
11. gprof只能在程序正常结束退出之后才能生成报告(gmon.out)。
a) 原因: gprof通过在atexit()里注册了一个函数来产生结果信息,任何非正常退出都不会执行atexit()的动作,所以不会产生gmon.out文件。
b) 程序可从main函数中正常退出,或者通过系统调用exit()函数退出。
10 多线程应用
gprof 不支持多线程应用,多线程下只能采集主线程性能数据。原因是gprof采用ITIMER_PROF信号,在多线程内只有主线程才能响应该信号。
采用什么方法才能够分析所有线程呢?关键是能够让各个线程都响应ITIMER_PROF信号。可以通过桩子函数来实现,重写pthread_create函数。
//////////////////// gprof-helper.c////////////////////////////
#define _GNU_SOURCE
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <pthread.h>
static void * wrapper_routine(void *);
/* Original pthread function */
static int (*pthread_create_orig)(pthread_t *__restrict,
__const pthread_attr_t *__restrict,
void *(*)(void *),
void *__restrict) = NULL;
/* Library initialization function */
void wooinit(void) __attribute__((constructor));
void wooinit(void)
{
pthread_create_orig = dlsym(RTLD_NEXT, "pthread_create");
fprintf(stderr, "pthreads: using profiling hooks for gprof/n");
if(pthread_create_orig == NULL)
{
char *error = dlerror();
if(error == NULL)
{
error = "pthread_create is NULL";
}
fprintf(stderr, "%s/n", error);
exit(EXIT_FAILURE);
}
}
/* Our data structure passed to the wrapper */
typedef struct wrapper_s
{
void * (*start_routine)(void *);
void * arg;
pthread_mutex_t lock;
pthread_cond_t wait;
struct itimerval itimer;
} wrapper_t;
/* The wrapper function in charge for setting the itimer value */
static void * wrapper_routine(void * data)
{
/* Put user data in thread-local variables */
void * (*start_routine)(void *) = ((wrapper_t*)data)->;start_routine;
void * arg = ((wrapper_t*)data)->;arg;
/* Set the profile timer value */
setitimer(ITIMER_PROF, &((wrapper_t*)data)->;itimer, NULL);
/* Tell the calling thread that we don't need its data anymore */
pthread_mutex_lock(&((wrapper_t*)data)->;lock);
pthread_cond_signal(&((wrapper_t*)data)->;wait);
pthread_mutex_unlock(&((wrapper_t*)data)->;lock);
/* Call the real function */
return start_routine(arg);
}
/* Our wrapper function for the real pthread_create() */
int pthread_create(pthread_t *__restrict thread,
__const pthread_attr_t *__restrict attr,
void * (*start_routine)(void *),
void *__restrict arg)
{
wrapper_t wrapper_data;
int i_return;
/* Initialize the wrapper structure */
wrapper_data.start_routine = start_routine;
wrapper_data.arg = arg;
getitimer(ITIMER_PROF, &wrapper_data.itimer);
pthread_cond_init(&wrapper_data.wait, NULL);
pthread_mutex_init(&wrapper_data.lock, NULL);
pthread_mutex_lock(&wrapper_data.lock);
/* The real pthread_create call */
i_return = pthread_create_orig(thread,
attr,
&wrapper_routine,
&wrapper_data);
/* If the thread was successfully spawned, wait for the data
* to be released */
if(i_return == 0)
{
pthread_cond_wait(&wrapper_data.wait, &wrapper_data.lock);
}
pthread_mutex_unlock(&wrapper_data.lock);
pthread_mutex_destroy(&wrapper_data.lock);
pthread_cond_destroy(&wrapper_data.wait);
return i_return;
}
///////////////////
然后编译成动态库 gcc -shared -fPIC gprof-helper.c -o gprof-helper.so -lpthread -ldl
使用例子:
/////////////////////a.c/////////////////////////////
#include <stdio.h>;
#include <stdlib.h>;
#include <unistd.h>;
#include <pthread.h>;
#include <string.h>;
void fun1();
void fun2();
void* fun(void * argv);
int main()
{
int i =0;
int id;
pthread_t thread[100];
for(i =0 ;i< 100; i++)
{
id = pthread_create(&thread[i], NULL, fun, NULL);
printf("thread =%d/n",i);
}
printf("dsfsd/n");
return 0;
}
void* fun(void * argv)
{
fun1();
fun2();
return NULL;
}
void fun1()
{
int i = 0;
while(i<100)
{
i++;
printf("fun1/n");
}
}
void fun2()
{
int i = 0;
int b;
while(i<50)
{
i++;
printf("fun2/n");
//b+=i;
}
}
///////////////
gcc -pg a.c gprof-helper.so
运行程序:
./a.out
分析gmon.out:
gprof -b a.out gmon.out