天天看點

性能優化之C++ Profiler

最近在做有關性能優化方面的東西,其中使用到了Profiler,就目前來看除了glibc中自帶的gprof以外,還有一個使用的比較廣泛的由google開發的GooglePerformance tools,在這篇博文中,我們就來學學gprof這個性能調試工具吧,在介紹gprof之前,我們有必要來思考下我們優化的性能主要包括哪些部分,這部分内容可以參考這篇博文http://coolshell.cn/articles/7490.html,簡要地說性能主要包括了:1)吞吐量,2)時延;一般來說,一個系統的性能受到這兩個條件的限制,缺一不可。比如,我的系統可以頂得住一百萬的并發,但是系統的延遲是2分鐘以上,那麼,這個一百萬的負載毫無意義。系統延遲很短,但是吞吐量很低,同樣沒有意義。是以,一個好的系統的性能測試必然受到這兩個條件的同時作用。 有經驗的朋友一定知道,這兩個東西的一些關系:Throughput越大,Latency會越差。因為請求量過大,系統太繁忙,是以響應速度自然會低。Latency越好,能支援的Throughput就會越高。因為Latency短說明處理速度快,于是就可以處理更多的請求。這部分内容是完全參考了cool shell的博文,好了,介紹到此,接下來,我們就來看看gprof這個工具吧,測試代碼如下: 

#include <stdio.h>
#include <stdlib.h>
int worker1()
{
    int i=0,j=0;
    while(i++ < 4000000)
        j+=i;
}

int worker2()
{
    int i=0,j=0;
    while(i++ < 400000)
        j+=i;
}

int main(int argc,char** argv)
{
    int num = 0;
    if(argc <2)
    {
        printf("usage %s num\n",argv[0]);
        exit(-1);
    }
    num = atoi(argv[1]);
    while(num--)
    {
        worker1();
        worker2();
    }
}
           

以上是測試代碼,首先通過g++ -o Test Test.cpp -pg -lc,來編譯,編譯完成時會生成gmon.out檔案,有了這個檔案,我們就可以通過gprof Test gmon.out +編譯選項來檢視程式的性能情況

1)選項 -p

Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total
 time   seconds   seconds    calls  ms/call  ms/call  name
 90.65     98.64    98.64    10000     9.86     9.86  worker1()
  9.35    108.81    10.17    10000     1.02     1.02  worker2()
           

從上述代碼中,可以看出woker1的操作耗時基本上是worker2的10倍,并且最主要的耗時是在疊加計算上,

2)選項 -q

Call graph (explanation follows)


granularity: each sample hit covers 4 byte(s) for 0.01% of 108.81 seconds

index % time    self  children    called     name
                                                 <spontaneous>
[1]    100.0    0.00  108.81                 main [1]
               98.64    0.00   10000/10000       worker1() [2]
               10.17    0.00   10000/10000       worker2() [3]
-----------------------------------------------
               98.64    0.00   10000/10000       main [1]
[2]     90.7   98.64    0.00   10000         worker1() [2]
-----------------------------------------------
               10.17    0.00   10000/10000       main [1]
[3]      9.3   10.17    0.00   10000         worker2() [3]
-----------------------------------------------
           

這個選項相比于-p,其更加詳細,從上面可以看出main函數基本上的耗時都在函數調用上,而worker1的耗時是worker2的10倍,

從上述代碼測試中,有一個局限性:隻能測試使用者自行寫的程式,而對于通過共享庫而使用的一個函數是否也可以檢查出來呢,目前在希望從共享庫(包括 C 庫 libc.a)中獲得剖析資訊,就需要使用

-pg

來編譯這些庫。幸運的是,很多發行版都提供了已經啟用代碼剖析支援而編譯的 C 庫版本(libc_p.a)。下面我們就來看看如何通過共享庫來擷取程式的各個部分的性能情況,代碼如下:

#include <stdio.h>
int a(void) {
  sleep(1);
  return 0;
}
int b(void) {
  sleep(4);
  return 0;
}
int main(int argc, char** argv)
{
   int iterations;
   if(argc != 2)
   {
      printf("Usage %s <No of Iterations>\n", argv[0]);
      exit(-1);
   }
   else
      iterations = atoi(argv[1]);
   printf("No of iterations = %d\n", iterations);
   while(iterations--)
   {
      a();
      b();
   }
}
           

測試結果(g++ -o Test Test.cpp -pg -lc_p;./Test 30)

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      120     0.00     0.00  sigprocmask
  0.00      0.00     0.00       61     0.00     0.00  __libc_sigaction
  0.00      0.00     0.00       61     0.00     0.00  sigaction
  0.00      0.00     0.00       60     0.00     0.00  nanosleep
  0.00      0.00     0.00       60     0.00     0.00  sleep
  0.00      0.00     0.00       30     0.00     0.00  a
  0.00      0.00     0.00       30     0.00     0.00  b
  0.00      0.00     0.00       21     0.00     0.00  _IO_file_overflow
  0.00      0.00     0.00        3     0.00     0.00  _IO_new_file_xsputn
  0.00      0.00     0.00        2     0.00     0.00  _IO_new_do_write
  0.00      0.00     0.00        2     0.00     0.00  __find_specmb
  0.00      0.00     0.00        2     0.00     0.00  __guard_setup
  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  __cxa_atexit
  0.00      0.00     0.00        1     0.00     0.00  __errno_location
  0.00      0.00     0.00        1     0.00     0.00  __new_exitfn
  0.00      0.00     0.00        1     0.00     0.00  __strtol_internal
  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  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  moncontrol
  0.00      0.00     0.00        1     0.00     0.00  new_do_write
  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  vfprintf
  0.00      0.00     0.00        1     0.00     0.00  write
           

盡管 profiler 已經記錄了每個函數被調用的确切次數,但是為這些函數記錄的時間(實際上是所有函數)都是 0.00。這是因為

sleep

函數實際上是執行了一次對核心空間的調用,進而将應用程式的執行挂起了,然後有效地暫停執行,并等待核心再次将其喚醒。由于花在使用者空間執行的時間與花在核心中睡眠的時間相比非常小,是以就被取整成零了。其原因是 gprof 僅僅是通過以固定的周期對程式運作時間 進行采樣測量來工作的。是以,當程式不運作時,就不會對程式進行采樣測量。

總結

      盡管 gprof 存在上面的限制,但是它對于優化代碼來說依然是個非常有用的工具,如果您的代碼大部分是使用者空間 CPU 密集型的,它的用處就更加明顯。首先使用

time

來運作程式進而判斷 gprof 是否能産生有用資訊是個好主意。好了,本篇博文到此結束,下篇我們會通過幾個執行個體來進一步學習使用gprof工具進行性能調優。

如果需要,請注明轉載,多謝