gcc profiling的工作原理

转载时请注明出处和作者联系方式:http://blog.csdn.net/absurd
作者联系方式:Li XianJing <xianjimli at hotmail dot com>
更新时间:2006-1-8
 
gcc profiling是一个常用的功能,本来没有什么好说的。不过最近想知道GTK+ + DirectFB应用程序起动缓慢的原因,打算让gcc profiling帮帮忙,结果却遇到一个难题,让人百思不得其解。我自然不会轻易放弃,花了些时间去研究它的工作原理。这里做一下笔记,望高手指点。
 
所谓的profiling,就是分析程序中的各个函数的调用次数和运行时间,以找到程序的热点,为优化程序提供依据。gcc的profiling功能更强大一点,它还可以显示调用关系(call graph)。它的用法如下:
1.        编译/链接时加-pg标志。
gcc -pg -g main.c test.c -o test
2.        运行程序。
./test
3.        分析数据。
gprof –b test
结果:
Each sample counts as 0.01 seconds.
 %   cumulative   self              self     total          
 time   seconds   seconds    calls ms/call ms/call name   
100.72      0.07     0.07        1    70.50    70.50 test
 
够简单吧! 不过当我把test.c编译成一个共享库时,就遇到了麻烦:
1.         编译。
gcc -pg -g test.c -shared -o libtest.so
gcc -pg -g main.c -L./ -ltest -o test
2.         运行程序。
./test
3.         分析数据。
gprof -b test
结果:
Each sample counts as 0.01 seconds.
 no time accumulated
 
 %   cumulative   self              self     total          
 time   seconds   seconds    calls Ts/call Ts/call name   
 
前面的分析数据现在没了,你说奇怪不?难道gcc profiling不支持共享库?查了一个资料,即没有找到gcc profiling不支持共享库的论断,也没有找到支持共享库的方法。只好去自己去分析。
 
1.         先反汇编,观察gcc对源代码的处理。
objdump -S test
我们可以看到,gcc在每个函数里都加了call   804841c mcount@plt。可以猜测mcount是用来统计函数调用次数的。
      
2.         用gdb在mcount处设置断点。
Breakpoint 3, 0x001de9b0 in mcount () from /lib/libc.so.6
正如所料的,mcount是在libc中实现的。
 
3.         在glibc中找到mcount的源代码(gmon/mcount.c)
mcount的实现有点复杂,它的功能正是增加函数调用次数count的。
 
4.         mcount 记录了函数的调用次数,但运行时间是如何确定的呢?接下又看了gmon.c。
l          monstartup是初始化函数。它有两个参数:lowpc和 highpc。我们知道pc是程序计数器,lowpc/ highpc指向代码段的开始和结束。在csu/gmon-start.c中得到了证实:lowpc=_start,而highpc=etext。
l          mcleanup是退出函数。它负责保存profiling数据。数据分为histogram、call-graph和basic-block三类。Histogram描述每一段指令执行的情况,call-graph描述调用关系和调用次数,basic-block描述每条语句的执行情况。gcc 4.00并不支持-a选项,估计basic-block不会工作。
l          moncontrol是一个有趣的函数,它真正控制profiling的开始和结束。它用不同的参数调用了__profil,以决定是开始还是结束。起初我猜测__profil是系统调用,不过在kernel里没有发现它的实现。
 
5.         最后在sysdeps/posix/profil.c中找到了__profil的实现。
l          __profil 的实现很简单,用setitimer设置一个定时器,定时器到了之后,系统会给该进程发送SIGPROF信号。
l          在profil_counter是SIGPROF信号的处理函数,profil_counter调用profil_count记录数据。
6.         可能只有明白了__profil的参数后才会清楚profil_count的实现。
l          sample_buffer: 很明显是用于记录采样数据的buffer,在profil_count函数中也可以得到证实。
l          size: sample_buffer的大小。
l          offset: 是代码段的起始位置,在moncontrol中可以得到证实,offset= lowpc。
l          scale: 这个参数有点让人费解了。再回去看看profil_count的实现,才知道profil_count中是以64K长度为统计单位的,64K长的代码可以包含上百个数函数,所以无疑粒度太大了。scale就是用来控制统计粒度的,scale=1表示以64K为采样单位,scale=64K表示以一个字节为采样单位。实际的采样粒度是在monstartup中指定的,它根据代码段的大小决定。
 
有了这些数据和setitimer的时间间隔,就不难得到profiling数据。
 
7.         我们知道了gcc profiling的工作原理,再回过头来看看原来的问题: gcc profiling是否支持共享库。答案是:不支持。证据有:
l          monstartup的参数lowpc和highpc,限制了只能对一个代码段进行profiling。而共享库的代码段往往是映射到不同的区间的。
l          profil_count 中的(i < nsamples)条件,过滤了对共享库函数的时间统计。
l          mcount中的(frompc > p->textsize) 条件,过滤了对共享库函数的调用次数统计。
 
从代码可以看出gcc profiling对多线程的处理也有问题。多线程和共享库这么重要的问题都不解决,那gcc profiling到底有多用处?不明白gcc profiling的实现者是如何考虑的。通过LD_PRELOAD自己实现mcount和SIGPROF信号处理函数并不复杂,但是真的非要自己写吗?
 
~~end~~
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值