GNU profiler(gprof)使用介绍及遇到的问题总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Chengzi_comm/article/details/56839968
最近实习,导师让做一个服务器的优化工作。由于自己没有做过相关工作,就在网上看能不能找一些工具,帮我先找出服务器性能瓶颈卡在什么地方。
当看到gprof之后,发现这就是我想要找。下面会给出Gprof的介绍及使用方式,然后给出我在使用gprof的过程中遇到的困难以及是如何解决的。

使用平台:CentOS 64 (ps: gprof也支持Windows,但我没在Windows上试过。)

1、gprof简单介绍:

 如果没有测试工具可以使用,改进应用程序的性能是非常难做的,因为究竟程序中是哪些函数消耗掉了大部分执行时间、又有哪些函数的调用次数最多
 (ps:调用次数最多不一定就最耗时)通常很难给出结论。
 GNU 编译器工具包所提供了一种剖析工具 GNU profiler(gprof)。gprof 可以为Linux平台上的程序精确分析性能瓶颈。 可以显示程序运行
 的“flat profile”,包括每个函数的调用次数,每个函数消耗的处理器时间。也可以显示“call graph”(调用图),
 包括函数的调用关系,每个函数调用花费了多少时间。还可以显示“注释的源代码”,是程序源代码的一个复本,标记有程序中每行代码的执行次数。

2、使用步骤:

  • 使用-pg选项编译链接程序,生成可执行程序; 
  • 运行可执行程序,程序正常退出,生成gmon.out文件,(ps:如果当前目录之前有gmon.out文件,之前的文件会被覆盖);
  • 使用gprof工具分析生成的gmon.out文件,得出一份统计报告。

3、具体使用gprof:

下面编写一个小程序来使用一下gprof:

1. 编写test.cpp;

#include <iostream>
using namespace std;


// do nothing
void Loop()
{
    int count = 0;
    for(; count < 20000000; ++count);
}

// call Loop inside
void Loop1()
{
    int count = 0;
    for(; count < 60000000; ++count);

    Loop();
}

void Loop2()
{
    int count = 0;
    for(; count < 20000000; ++count);
}

int main()
{
    Loop1();
    Loop2();
    Loop2();

    return 0;
}

2. 在test.cpp目录下执行命令:$g++ -pg -o test test.cpp,生成test可执行文件;

3. 运行test可执行文件,程序退出之后,会在当前目录生成gmon.out文件;

4. 使用gprof工具生成测试报告, 执行命令 $gprof -b xxx gmon.out > report.txt

"-b" 选项表示不生成结果的解释信息,测试报告会简介一些,xxx是生成的可执行程序名,本例是test, 
report.txt是gprof生成的测试报告,这个文件名可以任意起;

下面是生成的测试报告:
这里写图片描述

上图主要有两个表,第一个是flat profile,里面是每个函数的运行时间以及次数,第二个是call graph,表示函数的调用关系。

各个项的含义:

1、flat profiler:

%time 函数以及衍生函数(函数内部再次调用的子函数)所占的总运行时间的百分比
cumulative seconds 函数累计执行的时间
self seconds 函数执行占用的时间 (不包括衍生函数花费的时间)
calls 函数的调用次数
self ms/call 每一次调用函数花费的时间microseconds,不包括衍生函数的运行时间
total ms/call 每一次调用函数花费的时间microseconds,包括衍生函数的运行时间
name 函数名称

2、call graph:

index 索引值
%time 函数消耗的时间占所有时间的百分比
self 函数本身花费的时间(不包括衍生函数)
children 衍生函数所花费的时间
called 调用次数
name 函数名

简单说明:

  • 各个列的含义,在gprof的输出结果中都有详细的说明。上面用gprof生成测试报告的命令中,如果去掉 -b 选项, 就会生成各个列的具体含义,但由于加上后测试报告内容太多且杂乱,读者可以根据具体需要进行取舍;
  • 可以从结果看一下每个函数循环的次数跟各自花费的时间是成正比的;
  • 对于库,gporf是不会统计里面的函数的运行信息的,因为链接库并没有使用 -pg 进行编译, 因此如果希望从链接库(比如libc.a)中获得剖析信息,就需要使用 -pg 来编译这些库。

4、原理:


  • 通过在编译和链接你的程序的时候(使用 -pg 编译和链接选项),gcc 在你应用程序的每个函数中都加入了一个名为mcount ( or “_mcount” , or “__mcount” , 依赖于编译器或操作系统)的函数,也就是说你的应用程序里的每一个函数都会调用mcount, 而mcount 会在内存中
    保存一张函数调用图,并通过函数调用堆栈的形式查找子函数和父函数的地址。这张调用图也保存了所有与函数相关的调用时间,调用次数等等的所有信息。 至于mount函数如何实现,这个我就不得而知了。
  • 使用gprof的体会:在程序执行完后,生成gmon.out文件,其实在生成该文件以及之前的时间,都没有用到gprof工具,只是在g++的选项中用到了 -pg,所以在函数中加入代码(总共用到了三个函数【monstartup、_mcount、_mcleanup 】)是由g++来完成的。gprof工具只是解析了运行程序后
    生成的gmon.out文件;并通过自己的一些选项来控制生成的测试报告。需要注意的是程序必须是正常退出或者通过exit调用退出,因为只要在exit() 被调用时才会触发程序写gmon.out文件。

    这里有篇文章比较好的:http://blog.csdn.net/absurd/article/details/1477532?locationNum=3&fps=1

    5、生成图形化报告文件:

    函数调用关系的图形化需要用到两个东西:

    • graphviz-2.18.tar.gz (解压后需安装【进入解压后的文件夹一次执行 $ ./configure、make、make install 即可】安装需要root权限);
    • gprof2dot.py文件 (放到工程根目录即可)。

    以上两个文件在网上搜一下都可以下载到,这里就不给链接了。

    生成响应的png类型的图形文件:
    还是上面的程序,运行命令
    $gprof ./可执行文件名 | ./gprof2dot.py -n0 -e0 | dot -Tpng -o 文件名.png

    扫描二维码关注公众号,回复: 3818379 查看本文章

    函数调用关系如下图:
    这里写图片描述

    如果系统比较复杂,函数调用关系很大,直接生成png类型的图像很大,是会失败的。这时候可以生成pdf类型的,运行下面的命令即可:
    $gprof ./可执行文件名 | ./gprof2dot.py -n0 -e0 | dot -Tpdf -o 文件名.pdf

    6、优缺点:

    优点:

    • Linux系统下系统自带gprof命令,而且只需给编译器传入 -pg 选项即可,使用方便;
    • 统计信息比较详细。不仅能统计出一个函数自己,不包括衍生函数(这个函数内部调用的函数)运行的时间和次数,还能统计出各个衍生函数总共调用了多少次,在这个函数内部调用了多少次,以及所有衍生函数花费的时间。

    缺点:

    • 需要重新编译链接源文件才行;
    • 统计的最小时间单位太长了,gprof统计的时间最小时间长度是0.01秒,即10毫秒,对于服务器程序来说,10毫秒已经是很长时间了。也就是说大部分函数gprof统计出来的时间都是0,这时候调用时间就不准确了,但可以参考一下调用次数;
    • 网上说gprof不支持多线程,缘故是gprof使用ITIMER_PROF定时器。但我使用posix写了两个线程,确实是可以正确统计线程的执行信息的。对于不支持多线程这个结论还需要商榷;
    • 不支持多进程,如果分析多进程程序则可能一个进程的gmont.out文件会覆盖另一个进程的gmont.out文件。有的解决方法是在执行程序之前执行 export GMON_OUT_PREFIX=x.out 命令,则之后生成的文件名就如x.out.pid,多进程的gmon.out就不会相互覆盖。 这个我还没有试过;
    • 只能分析应用程序在运行过程中所消耗掉的用户时间,无法得到程序内核空间的运行时间。这个也有一个解决办法,在执行程序时使用time,即执行 $time ./可执行文件名,程序结束后会有一个总的运行时间和系统运行时间以及用户态时间;
    • 对于服务器程序,一般是不会退出的。如果程序没有正常退出,g++是不会生成最后的gmon.out统计结果的。所以gprof也就用不上了;
    • 对于这种情况有一个解决办法:在程序开始时注册一个信号捕获函数,如果收到注册的信号(比如SIGINT Ctrl-C),程序执行exit()的话,最后也是可以生成gmon.out的。
         void SignalHandler(int signo)
         {
              exit(0);
         }
    
         int main(int argc, char ** argv)
         {
              signal(SIGINT, SignalHandler);
    
              ...
         }

    尽管gprof有这许多缺点,但它强大的统计能力还是能深深吸引一个开发人员。

    7、遇到的问题以及解决办法:

    • 怎么把 “-pg” 选项传给编译器?(对于使用CMakeList.txt生成的Makefile)

     对于使用cmake构建可执行文件的系统来说,生成的Makefile里面是没有gcc或者g++等编译器的,这个时候怎么添加 -pg 选项给编译器。可以通过使用cmake的预定义宏来解决。这个问题在stackoverflow上也有:
    http://stackoverflow.com/questions/23560214/how-to-set-flags-of-g-using-cmake-such-that-gprof-can-demangle
    http://stackoverflow.com/questions/26491948/how-to-use-gprof-with-cmake
    对于上面那个简单的C程序来说:可以编写如下CMakeList.txt文件(写好CMakeList.txt文件后,执行 $cmake . ; make 来生成可执行文件test,ps:cmake后面要跟上CMakeList.txt文件的路径,对于上面的测试文件来说,在当前目录,所以是一个 “.”)

    cmake_minimum_required(VERSION 2.6)
    project(test)
    
    #set flags for use gprof
    set(CMAKE_CXX_FLAGS  "${CMAKE_CXX_FLAGS} -pg")
    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pg")
    
    add_executable(test test.cpp)
    • Glog的初始化会导致gprof停止工作,最后没有生成gmon.out文件?
     这个问题我还没有解决,在测试过程中,我也只是把初始化去掉来测的。接下来我也会看一下这个问题的原因,大家知道的话也可以在下面说出来。
    

    猜你喜欢

    转载自blog.csdn.net/Chengzi_comm/article/details/56839968
    GNU