gprof工具使用介绍

 一、gprof介绍

       gprofGNUprofiler工具。可以显示程序运行的“flat profile”,包括每个函数的调用次数,每个函数消耗的处理器时间。也可以显示调用图,包括函数的调用关系,每个函数调用花费了多少时间。还可以显示注释的源代码,是程序源代码的一个复本,标记有程序中每行代码的执行次数

二、Gprof功能:

    打印出程序运行中各个函数消耗的时间,可以帮助程序员找出众多函数中耗时最多的函数。

    产生程序运行时候的函数调用关系,包括调用次数,可以帮助程序员分析程序的运行流程。有了函数的调用关系,这会让开发人员大大提高工作效率,不用费心地去一点点找出程序的运行流程,这对小程序来说可能效果不是很明显,但对于有几万,几十万代码量的工程来说,效率是毋庸置疑的!而且这个功能对于维护旧代码或者是分析OpenSource来说那是相当诱人的,有了调用图,对程序的运行框架也就有了一个大体了解,知道了程序的“骨架“,分析它也就不会再那么茫然,尤其是对自己不熟悉的代码和OpenSource。费话不多说了,让我们开始我们的分析之旅吧!

三、Gprof 实现原理:

   通过在编译和链接你的程序的时候(使用-pg 编译和链接选项),gcc 在你应用程序的每个函数中都加入了一个名为mcount( or “_mcount”  ,or  “__mcount” ,依赖于编译器或操作系统)的函数,也就是说你的应用程序里的每一个函数都会调用mcount, 而mcount会在内存中保存一张函数调用图,并通过函数调用堆栈的形式查找子函数和父函数的地址。这张调用图也保存了所有与函数相关的调用时间,调用次数等等的所有信息。

       程序运行结束后,会在程序退出的路径下生成一个 gmon.out文件。这个文件就是记录并保存下来的监控数据。可以通过命令行方式的gprof或图形化的Kprof来解读这些数据并对程序的性能进行分析。

       另外,如果想查看库函数的profiling,需要在编译是再加入“-lc_p”编译参数代替“-lc”编译参数,这样程序会链接libc_p.a 库,才可以产生库函数的profiling信息。如果想执行一行一行的profiling,还需要加入“-g”编译参数。

四、gprof 的适用范围

       gprof可以用来分析系统在运行时各函数调用的次数,耗时等情况,可以方便地帮助我们定位系统的瓶颈,同时也能让我们知道对程序的那个位置就行优化能够带来尽可能大的性能提升。gprof 优化尤其适用于CPU、内存密集性的应用模块

五、gprof的安装使用

       目前我们的linux主机上大多都安装了gprof,详细的参数等可以通过mangprof查看。需要重点指出的是,目前我们线上的gprof对多线程的支持不好,直接调用只能得到主线程的相关调用情况。根据相关资料,原因为gprof采用ITIMER_PROF 信号,在多线程内,只有主线程才能响应该信号。为此,需要做一些额外的工作。使用提供的gprof-helper.c,将其编译为so命令为:gcc-shared -fPICgprof-helper.c -o gprof-helper.so -lpthread-ldl

       这个库的作用实际上实现一个pthread_create 的钩子程序,这样我们在调用pthread_create函数的时候就会调用到这个库中提供的pthread_create的函数,从而实现在多线程情况下统计运行时的相关信息。

       在实际使用中,方法比较简单,在我们自己的程序的makefile文件中,加上编译的选项 -pg,并加上那个动态链接库。如gcc-pg imbs_main.cpp ../gprof-helper.so$(INCLUDE) $(LDFLAGS)$(LDLIBS)

       这样在编译后会生成一个a.out 文件。这个文件就是包含了相关统计功能的可执行文件,和我们正常编译的程序在对外行为上是完全一致的。

参考资料: http://blog.csdn.net/baqiao10/articles/443495.aspx

      程序运行并“正常”退出后,会生成一个gmon.out文件,这个就是运行时的统计文件。使用命令gprof-b a.out gmon.out 就可以将最终我们readable的信息输出来。这些信息可以作为我们优化的依据。

      注意:上面提到的“正常”退出是指程序是按照自身的运行逻辑正常退出的,如果直接killall-9 是不能得到统计结果的而我们通常的程序都是在循环中长时间运行,所以,实际中采用了相应 SIGTERM信号的方式,使用killall-s15 ,发送SIGTERM信号给程序,程序中会有相应的函数捕捉该信号,捕捉到该信号后,置一个退出标记,这样我们就可以控制程序按照既定的逻辑在处理完一次完整的工作后正常的退出

      这又引出了另一个问题,实际上我们现在上线程序,重启程序的时候,通常都是使用killall-9来停止原有程序的。这实际上是存在较大风险的,举例来说,如果程序在执行时存在一些持久化的操作,比如写磁盘,同时,写磁盘操作是多次完成,比如先写数据、再写索引等,这应该是一个在逻辑上的原子操作,那么killall-9的随机性可能破坏其原子性,从而造成潜在的数据不一致,如在我们常用的transfer中就存在这种数据不一致的隐患。虽然出现的概率不是很高,但是,长期的积累这种不一致性是会慢慢体现出来的。

 

六、Gprof基本用法:

1.使用-pg选项编译和链接你的应用程序。
2.
执行你的应用程序,使之运行完成后生成供gprof分析的数据文件(默认是gmon.out)。
3.
使用gprof程序分析你的应用程序生成的数据,例如:gprof a.out gmon.out

举例

gcc-Wall -pg -otesttest.c              //程序文件名称 test.编译时使用 –pg

gprof输出分析

在gmon.out文件产生之后,可以通过GNU binutils中提供的工具gprof来分析数据,转换成容易阅读、理解的格式。

一般用法:

# gprof Binary-file gmon.out >report.txt

其中,Binary-file指的是所运行的程序(也可以是程序调用到的库文件),gmon.out就是前面所输出的那个文件,report.txt就是生成的分析报告了。Gprof提供了丰富的参数选项,以控制报告输出的内容。

多进程

如果用gprof分析多进程程序,则可能一个进程的gmon.out覆盖另一个进程的gmon.out,解决方法是在执行程序之前执行:export GMON_OUT_PREFIX=x.out则之后生成的文件名就如x.out.pid,多进程的gmon.out就不会相互覆盖。

多线程

gprof无法分析多线程程序。缘故是gprof使用ITIMER_PROF定时器,当超时时由内核向应用程序发送信号。但多线程程序只有主线程接收ITIMER_PROF。这里有一个简单的实现方法:对pthread_create进行包装,并以动态库的形式在程序运行前加载。我通过上文的描述,整理了一个gprof分析多线程程序的程序,可供参考。 

$ gcc -shared -fPIC gprof_helper.c -o ghelper2.so -lpthread -ldl  # create ghelp2.so
$ gcc test.c -lpthread
$ ./a.out 
    hello gprof     # output
$ LD_PRELOAD=./ghelper2.so ./a.out 
    pthread: using profiling hooks for gprof    # output
    hello gprof                                 # output

# no time accumulated
Each sample counts as 0.01 seconds.
no time accumulated

gprof的输出表明没有时间被统计到。虽然函数调用次数是统计正确的,但没有时间(调用次数多未必就最耗时)。gprof显示每0.01秒采样一次,如果函数执行的时间都非常短,例如低于0.01秒,则统计不到任何时间。 

综述

  • gprof用于分析函数调用耗时,可用之抓出最耗时的函数,以便优化程序。
  • gcc链接时也一定要加-pg参数,以使程序运行结束后生成gmon.out文件,供gprof分析。
  • gprof默认不支持多线程程序,默认不支持共享库程序。
  1. gcc -pg 编译程序
  2. 运行程序,程序退出时生成 gmon.out
  3. gprof ./prog gmon.out -b 查看输出

注意事项

  • 程序如果不是从main return或exit()退出,则可能不生成gmon.out。
  • 程序如果崩溃,可能不生成gmon.out。
  • 测试发现在虚拟机上运行,可能不生成gmon.out。
  • 一定不能捕获、忽略SIGPROF信号。man手册对SIGPROF的解释是:profiling timer expired. 如果忽略这个信号,gprof的输出则是:Each sample counts as 0.01 seconds. no time accumulated.
  • 如果程序运行时间非常短,则gprof可能无效。因为受到启动、初始化、退出等函数运行时间的影响。
  • 程序忽略SIGPROF信号!  

參數說明

-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 顯示使用次數為零的常式(按照調用計數和累積時間計算)。

一般用法: gprof b 二進位程序 gmon.out >report.txt

報告說明

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 查詢。


gprof 的最大缺陷:它只能分析應用程序在運行過程中所消耗掉的用戶時間,無法得到程序內核空間的運行時間。通常來說,應用程序在運行時既要花費一些時間來運行用戶代碼,也要花費一些時間來運行 「系統代碼」,例如內核系統調用sleep()。

有一個方法可以查看應用程序的運行時間組成,在 time 命令下面執行程序。這個命令會顯示一個應用程序的實際運行時間、用戶空間運行時間、內核空間運行時間

如 time ./program

輸出:

real    2m30.295s

user    0m0.000s

sys     0m0.004s

gprof只能分析應用程序所消耗掉的用戶時間.


正常退出写法

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文件。 


HOWTO: using gprof with multithreaded applications 

http://sam.zoy.org/writings/programming/gprof.html

/* gprof-helper.c -- preload library to profile pthread-enabled programs
 *
 * Authors: Sam Hocevar <sam at zoy dot org>
 *          Daniel Jönsson <danieljo at fagotten dot org>
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the Do What The Fuck You Want To
 *  Public License as published by Banlu Kemiyatorn. See
 *  http://sam.zoy.org/projects/COPYING.WTFPL for more details.
 *
 * Compilation example:
 * gcc -shared -fPIC gprof-helper.c -o gprof-helper.so -lpthread -ldl
 *
 * Usage example:
 * LD_PRELOAD=./gprof-helper.so your_program
 */

#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;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值