目錄

廣告 AD

gperftools:好用工具大集合,tc_malloc、heap profiler 和 cpu profiler

gprof 在多執行續的狀況下,測量出的數據可能不精準

這時候就可以試試看 gperftools 的 CPU Profiler

也可以順便用用看 Heap Profiler

廣告 AD

gperftools

gperftools/gperftools

gperftools 包含個很多功能,有著在多執行續下高性能的 malloc 實作,也有針對 heap 的 profiler,對於執行上也有 CPU profiler 可以畫出 call graph 和時間。


下載 github 上的專案 gperftools/gperftools,可以使用 git clone,或是直接從網頁下載。

shell

git clone https://github.com/gperftools/gperftools

接著由於要產生 configure,我們要先安裝 autoconf、automake 和 libtool,已經安裝過的可以跳過。

shell

sudo apt-get install autoconf automake libtool

接著跑執行 autogen.sh 產生 configure:

shell

./autogen.sh

分析環境和創建 Makefile:

shell

./configure

我們就開始編譯吧:

shell

make

最後下指令安裝:

shell

sudo make install

Thread-Caching Malloc 是一個比 glibc 2.3 malloc 還快的 malloc,在 multi-threaded 的程式中也能降低 lock 的影響,詳細原理這邊不多做說明,使用方式很簡單,如下:

shell

gcc [...] -o <program> -ltcmalloc

Example:

shell

gcc main.c -o main -ltcmalloc

C

#include <gperftools/tcmalloc.h>

int main(){
  int* arr = (int*)tc_malloc(100 * sizeof(int));
  
  for(int i = 0; i < 100; ++i){
    arr[i] = i*2;
  }

  free(arr);
}

Heap Profiler 可以幫助你看到 heap 在各個地方的使用狀況。

編譯時一樣要加上 tcmalloc,並且加入 -g 得到除錯訊息。

shell

gcc -g [...] -o <program> -ltcmalloc

接著執行時要設定 profiler 輸出檔案名稱:

shell

env HEAPPROFILE=<output file> ./<program>

執行完後要使用 pprof 來處理 profiler 的輸出

shell

pprof <program> <output file>  # 互動模式
pprof --text <program> <output file>  # 輸出成文字到 stdout
pprof --gv <program> <output file>  # 輸出成 Postscript 並顯示
pprof --dot <program> <output file>  # 輸出成 DOT 檔案到 stdout 
pprof --svg <program> <output file>  # 輸出成 SVG 檔案到 stdout

Example:

shell

gcc -g main.c -o main -ltcmalloc

shell

env HEAPPROFILE=main ./main

C

#include <stdlib.h>

int* gen100Int(){
  int* arr = (int*)malloc(100 * sizeof(int));
  return arr;
}

int* gen1000Int(){
  int* arr = (int*)malloc(1000 * sizeof(int));
  return arr;
}

int main(){
  int* arr100 = gen100Int();
  int* arr1000 = gen1000Int();
  free(arr100);
  free(arr1000);
}

text

Total: 0.0 MB
     0.0  90.9%  90.9%      0.0  90.9% gen1000Int
     0.0   9.1% 100.0%      0.0   9.1% gen100Int
     0.0   0.0% 100.0%      0.0 100.0% __libc_start_main
     0.0   0.0% 100.0%      0.0 100.0% _start
     0.0   0.0% 100.0%      0.0 100.0% main

上述的方式為測量整個程式,如果只想要測試某部分的話,也可以使用如下的方式,這樣直接執行就好:

shell

gcc -g main.c -o main -ltcmalloc

shell

./main

C

#include <stdlib.h>
#include <gperftools/heap-profiler.h>

int* gen100Int(){
  int* arr = (int*)malloc(100 * sizeof(int));
  if(IsHeapProfilerRunning()){
    HeapProfilerDump("middle");
  }
  return arr;
}

int* gen1000Int(){
  int* arr = (int*)malloc(1000 * sizeof(int));
  if(IsHeapProfilerRunning()){
    HeapProfilerDump("middle");
  }
  return arr;
}

int main(){
  int* arr1000_1 = gen1000Int();
  HeapProfilerStart("main_profile_file");
  int* arr100 = gen100Int();
  int* arr1000_2 = gen1000Int();
  HeapProfilerStop();
  free(arr100);
  free(arr1000);
}

Dumped in gen100Int()

Dumped in gen1000Int()


除了有 heap 的 profiler 之外,也有 CPU 的 Profiler,使用時要連結 profiler:

shell

gcc [...] -o <program> -lprofiler

並且執行時要加上

shell

env CPUPROFILE=<output_file> ./<program>

(這個我怎麼試也試不出來,只能用 include 的方式寫)

最後一樣透過 pprof 來分析輸出檔案:

shell

pprof <program> <output file>  # 互動模式
pprof --text <program> <output file>  # 輸出成文字到 stdout
pprof --gv <program> <output file>  # 輸出成 Postscript 並顯示
pprof --dot <program> <output file>  # 輸出成 DOT 檔案到 stdout 
pprof --svg <program> <output file>  # 輸出成 SVG 檔案到 stdout

pprof --focus=<re> <program> <output file>  # 只顯示特定 node,re 為正規表達式
pprof --ignore=<re> <program> <output file>  # 忽略特定 node,re 為正規表達式

pprof --lines <program> <output file>  # 逐行顯示結果
pprof --functions <program> <output file>  # 顯示每個 function 結果
pprof --files <program> <output file>  # 顯示每個檔案結果

一樣我們可以透過 function 來控制開始和結束的地方

Example:

shell

gcc -g main.c -o main -lprofiler

shell

./main

C

#include <gperftools/profiler.h>
#include <stdlib.h>
#include <stdio.h>

int run1(int* arr){
  for(int i = 1; i < 3000000; ++i){
    arr[i] ^= arr[i-1];
  }
  return arr[3000000-1];
}

int run2(int* arr){
  for(int i = 1; i < 10000000; ++i){
    arr[i] ^= arr[i-1];
  }
  return arr[10000000-1];
}

int main(){
  int* arr = (int*)malloc(sizeof(int) * 10000000);
  for(int i = 0; i < 10000000; ++i){
    arr[i] = i;
  }
  ProfilerStart("prof.out");
  int r1 = run1(arr);
  int r2 = run2(arr);
  printf("%d %d\n", r1, r2);
  ProfilerStop();
  free(arr);
}

函示分析

行分析

檔案分析


最後針對無法停下要一直運行的服務,我們可以傳送信號給程式,並在收到信號後開始蒐集 CPU 使用的訊息,再次收到信號後停止蒐集訊息並輸出。

shell

# 12 is user defined signal
killall -12 main  # start profiling
killall -12 main  # stop profiling

C

#include <gperftools/profiler.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>

int run1(int* arr){
  for(int i = 1; i < 3000000; ++i){
    arr[i] += arr[i-1];
  }
  return arr[3000000-1];
}

int run2(int* arr){
  for(int i = 1; i < 10000000; ++i){
    arr[i] += arr[i-1];
  }
  return arr[10000000-1];
}

void handle_signal(int sig){
  static int isRunning = 0;
  if(isRunning){
    isRunning = 0;
    ProfilerStop();
  }else{
    isRunning = 1;
    ProfilerStart("prof.out");
  }
}

int main(){
  signal(12, handle_signal);
  int* arr = (int*)malloc(sizeof(int) * 10000000);
  for(int i = 0; i < 10000000; ++i){
    arr[i] = i;
  }
  for(int i = 0; i < 10000; ++i){
    int r1 = run1(arr);
    int r2 = run2(arr);
    printf("%d %d\n", r1, r2);
  }
  free(arr);
}

如果出現了 error while loading shared libraries,那代表程式運行的時候找不到 shared library,我們可以在系統上設定 shared library 的位置,使用 ldconfig 後面加上你安裝的位置,預設是 usr/local/lib

shell

sudo ldconfig <Install Folder>

其實在安裝的時候也有顯示做法,除了直接去系統設定動態函示庫的位置,也可以藉由設定環境變數達到:

text

Libraries have been installed in:
   /usr/local/lib

If you ever happen to want to link against installed libraries
in a given directory, LIBDIR, you must either use libtool, and
specify the full pathname of the library, or use the '-LLIBDIR'
flag during linking and do at least one of the following:
   - add LIBDIR to the 'LD_LIBRARY_PATH' environment variable
     during execution
   - add LIBDIR to the 'LD_RUN_PATH' environment variable
     during linking
   - use the '-Wl,-rpath -Wl,LIBDIR' linker flag
   - have your system administrator add LIBDIR to '/etc/ld.so.conf'

廣告 AD