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

gprof 在多執行續的狀況下,測量出的數據可能不精準
這時候就可以試試看 gperftools 的 CPU Profiler
也可以順便用用看 Heap Profiler
gperftools
gperftools 包含個很多功能,有著在多執行續下高性能的 malloc 實作,也有針對 heap 的 profiler,對於執行上也有 CPU profiler 可以畫出 call graph 和時間。
Build & Install
下載 github 上的專案 gperftools/gperftools,可以使用 git clone
,或是直接從網頁下載。
git clone https://github.com/gperftools/gperftools
接著由於要產生 configure,我們要先安裝 autoconf、automake 和 libtool,已經安裝過的可以跳過。
sudo apt-get install autoconf automake libtool
接著跑執行 autogen.sh
產生 configure:
./autogen.sh
分析環境和創建 Makefile:
./configure
我們就開始編譯吧:
make
最後下指令安裝:
sudo make install
TC Malloc
Thread-Caching Malloc 是一個比 glibc 2.3 malloc 還快的 malloc,在 multi-threaded 的程式中也能降低 lock 的影響,詳細原理這邊不多做說明,使用方式很簡單,如下:
gcc [...] -o <program> -ltcmalloc
Example:
gcc main.c -o main -ltcmalloc
#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 Profiler 可以幫助你看到 heap 在各個地方的使用狀況。
編譯時一樣要加上 tcmalloc
,並且加入 -g 得到除錯訊息。
gcc -g [...] -o <program> -ltcmalloc
接著執行時要設定 profiler 輸出檔案名稱:
env HEAPPROFILE=<output file> ./<program>
執行完後要使用 pprof
來處理 profiler 的輸出
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:
gcc -g main.c -o main -ltcmalloc
env HEAPPROFILE=main ./main
#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);
}
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

上述的方式為測量整個程式,如果只想要測試某部分的話,也可以使用如下的方式,這樣直接執行就好:
gcc -g main.c -o main -ltcmalloc
./main
#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()
CPU Profiler
除了有 heap 的 profiler 之外,也有 CPU 的 Profiler,使用時要連結 profiler:
gcc [...] -o <program> -lprofiler
並且執行時要加上
env CPUPROFILE=<output_file> ./<program>
(這個我怎麼試也試不出來,只能用 include 的方式寫)
最後一樣透過 pprof 來分析輸出檔案:
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:
gcc -g main.c -o main -lprofiler
./main
#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 使用的訊息,再次收到信號後停止蒐集訊息並輸出。
# 12 is user defined signal
killall -12 main # start profiling
killall -12 main # stop profiling
#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
如果出現了 error while loading shared libraries,那代表程式運行的時候找不到 shared library,我們可以在系統上設定 shared library 的位置,使用 ldconfig 後面加上你安裝的位置,預設是 usr/local/lib
。
sudo ldconfig <Install Folder>
其實在安裝的時候也有顯示做法,除了直接去系統設定動態函示庫的位置,也可以藉由設定環境變數達到:
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'
Reference
如果你覺得這篇文章有用 可以考慮贊助飲料給大貓咪