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;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章