它的主要功能就是通過采樣的方式,給程序中cpu的使用情況進(jìn)行“畫像”,通過它所輸出的結(jié)果,我們可以對(duì)程序中各個(gè)函數(shù)(得到函數(shù)之間的調(diào)用關(guān)系)耗時(shí)情況一目了然。在對(duì)程序做性能優(yōu)化的時(shí)候,這個(gè)是很重要的,先把最耗時(shí)的若干個(gè)操作優(yōu)化好,程序的整體性能提升應(yīng)該十分明顯,這也是做性能優(yōu)化的一個(gè)最為基本的原則—先優(yōu)化最耗時(shí)的。
1、怎么安裝?
1、下載gperftools
Wget https://code.google.com/p/gperftools/downloads/detail?name=gperftools-2.0.tar.gz
2、tar –xzf gperftools-2.0.tar.gz
3、cd gperftools-2.0
4、./configure --prefix=/usr/local –enable-frame-pointers
5、make && make install
ps:編譯時(shí)打開了 –enable-frame-pointers ,這要求被測(cè)試的程序在編譯時(shí)要加上gcc編譯選項(xiàng),否則某些多線程程序可能會(huì) core:
CCFLAGS=-fno-omit-frame-pointer
ps:perftools對(duì)2.4內(nèi)核的多線程支持不是很好,只能分析主線程,但是2.6內(nèi)核解決了這個(gè)問題。
安裝圖形化分析工具kcachegrind:
kcachegrind用來分析產(chǎn)生的profiling文件,linux環(huán)境下使用。
kcachegrind install:sudo apt-get install kcachegrind
2、Google perftools怎么用的?
方法有三種:
1、直接調(diào)用提供的api:這種方式比較適用于對(duì)于程序的某個(gè)局部來做分析的情況,直接在要做分析的局部調(diào)用相關(guān)的api即可。
方式:調(diào)用函數(shù):ProfilerStart() and ProfilerStop()
2、鏈接靜態(tài)庫:這種方式是最為常用的方式,后面會(huì)有詳細(xì)的介紹。
方式:在代碼link過程中添加參數(shù) –lprofiler
For example:gcc […] -o helloworld –lprofiler
運(yùn)行程序:env CPUPROFILE=./helloworld.prof ./helloworld
指定要profile的程序?yàn)閔elloworld,并且指定產(chǎn)生的分析結(jié)果文件的路徑為./helloworld.prof
3、鏈接動(dòng)態(tài)庫:這種方式和靜態(tài)庫的方式差不多,但通常不推薦使用,除非使用者不想額外鏈一個(gè)靜態(tài)庫(因?yàn)殒溄屿o態(tài)庫會(huì)增大binary的大。┑那闆r,可以考慮使用這種方式。
方式:運(yùn)行時(shí)使用LD_PRELOAD,e.g. % env LD_PRELOAD="/usr/lib/libprofiler.so" <binary>(不推薦這種方式)。
Ps:env是linux下插入環(huán)境變量的shell命令
4、 查看收集數(shù)據(jù)結(jié)果
查看profile結(jié)果:pprof工具,它是一個(gè)perl的腳本,通過這個(gè)工具,可以將google-perftool的輸出結(jié)果分析得更為直觀,輸出為圖片、pdf等格式。
Ps:在使用pprof之前需要先安裝運(yùn)行per15,如果要進(jìn)行圖標(biāo)輸出則需要安裝dot,如果需要--gv模式的輸出則需要安裝gv。
調(diào)用pprof分析數(shù)據(jù)文件:
% pprof /bin/ls ls.prof
Enters "interactive" mode
% pprof --text /bin/ls ls.prof
Outputs one line per procedure
% pprof --gv /bin/ls ls.prof
Displays annotated call-graph via 'gv'
% pprof --gv --focus=Mutex /bin/ls ls.prof
Restricts to code paths including a .*Mutex.* entry
% pprof --gv --focus=Mutex --ignore=string /bin/ls ls.prof
Code paths including Mutex but not string
% pprof --list=getdir /bin/ls ls.prof
(Per-line) annotated source listing for getdir()
% pprof --disasm=getdir /bin/ls ls.prof
(Per-PC) annotated disassembly for getdir()
% pprof --text localhost:1234
Outputs one line per procedure for localhost:1234
% pprof --callgrind /bin/ls ls.prof
Outputs the call information in callgrind format
分析callgrind的輸出:
使用kcachegrind工具來對(duì).callgrind輸出進(jìn)行分析
e.g. % pprof --callgrind /bin/ls ls.prof > ls.callgrind
% kcachegrind ls.callgrind
3、舉例分析
事例一:cpu_profiler_example.cpp,在代碼中插入標(biāo)簽,可以針對(duì)某個(gè)函數(shù)進(jìn)行特定的profile
代碼如下:
關(guān)注兩個(gè)函數(shù):ProfilerStart() and ProfilerStop()
Makefile:
-L 動(dòng)態(tài)鏈接庫地址,但是有可能程序執(zhí)行的時(shí)候,找不到動(dòng)態(tài)鏈接庫,所以得
export LD_LIBRARY_PATH=LD_LIBRARY_PATH:"/home/work/alex/tools/gperftools/lib"
1)執(zhí)行./cpu_profile_example
生成一個(gè)性能數(shù)據(jù)文件: cpu_profiler_example_29502.prof
Ps:當(dāng)然指定性能數(shù)據(jù)文件生成的路徑和文件名:
CPUPROFILE=/tmp/profile ./myprogram
將在/tmp目錄下產(chǎn)生profile性能數(shù)據(jù)文件
2)分析性能數(shù)據(jù)
pprof -text cpu_profiler_example cpu_profiler_example_3875.prof
Text輸出結(jié)果分析:
14 2.1% 17.2% 58 8.7% std::_Rb_tree::find
含義如下:
14:find函數(shù)花費(fèi)了14個(gè)profiling samples
2.1%:find函數(shù)花費(fèi)的profiling samples占總的profiling samples的比例
17.2%:到find函數(shù)為止,已經(jīng)運(yùn)行的函數(shù)占總的profiling samples的比例
58:find函數(shù)加上find函數(shù)里的被調(diào)用者總共花費(fèi)的profiling samples
8.7%:find函數(shù)加上find函數(shù)里的被調(diào)用者總共花費(fèi)的profiling samples占總的profiling samples的比例
std::_Rb_tree::find:表示profile的函數(shù)
ps: 100 samples a second,所以得出的結(jié)果除以100,得秒單位
Ldd可以查看一個(gè)程序要鏈接那些動(dòng)態(tài)庫:
事例二:cpu_profiler_example.cpp,不需要在代碼里添加任何標(biāo)簽,將profile所有的函數(shù)。
代碼如下:
Makefile:
1)執(zhí)行程序,生成性能數(shù)據(jù)文件
CPUPROFILE=/tmp/profile ./cpu_profiler_example
2)分析數(shù)據(jù)文件
1)pprof -text cpu_profiler_example profile
2)命令行交互模式
事例三:由于我們的程序有可能是服務(wù)程序,而服務(wù)程序不會(huì)自動(dòng)執(zhí)行完退出,如果以ctrl+c退出也不是正常的exit(0)的方式退出,而這會(huì)導(dǎo)致我們?cè)趐rofile的時(shí)候,收集到的數(shù)據(jù)不全甚至是空的,采用如下解決辦法:
將ProfilerStart和ProfilerStop這2個(gè)函數(shù)封裝到兩個(gè)信號(hào)處理函數(shù)中,給服務(wù)程序發(fā)信號(hào)SIGUSR1,就開始profile,給服務(wù)程序發(fā)信號(hào)SIGUSR2,就停止profile。這樣我們可以隨時(shí)對(duì)程序進(jìn)行profiling,并獲得數(shù)據(jù)。
代碼如下:
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <unistd.h> 4 #include <signal.h> 5 #include <google/profiler.h> 6 7 //SIGUSR1: start profiling 8 //SIGUSR2: stop profiling 9 10 static void gprof_callback(int signum) 11 { 12 if (signum == SIGUSR1) 13 { 14 printf("Catch the signal ProfilerStart\n"); 15 ProfilerStart("bs.prof"); 16 } 17 else if (signum == SIGUSR2) 18 { 19 printf("Catch the signal ProfilerStop\n"); 20 ProfilerStop(); 21 } 22 } 23 24 static void setup_signal() 25 { 26 struct sigaction profstat; 27 profstat.sa_handler = gprof_callback; 28 profstat.sa_flags = 0; 29 sigemptyset(&profstat.sa_mask); 30 sigaddset(&profstat.sa_mask, SIGUSR1); 31 sigaddset(&profstat.sa_mask, SIGUSR2); 32 33 if ( sigaction(SIGUSR1, &profstat,NULL) < 0 ) 34 { 35 fprintf(stderr, "Fail to connect signal SIGUSR1 with start profiling"); 36 } 37 if ( sigaction(SIGUSR2, &profstat,NULL) < 0 ) 38 { 39 fprintf(stderr, "Fail to connect signal SIGUSR2 with stop profiling"); 40 } 41 } 42 43 int loopop_callee() 44 { 45 int n=0; 46 for(int i=0; i<10000; i++) 47 { 48 for(int j=0; j<10000; j++) 49 { 50 n |= i%100 + j/100; 51 } 52 } 53 return n; 54 } 55 56 int loopop() 57 { 58 int n=0; 59 while(1) 60 { 61 for(int i=0; i<10000; i++) 62 { 63 for(int j=0; j<10000; j++) 64 { 65 n |= i%100 + j/100; 66 } 67 } 68 printf("result: %d\n", (loopop_callee)() ); 69 } 70 return n; 71 } 72 73 int main(int argc,char** argv) 74 { 75 char program[1024]={0}; 76 //snprintf(program,1023,"%s_%d.prof",argv[0],getpid()); 77 setup_signal(); 78 printf("result: %d\n", (loopop)() ); 79 return 0; 80 }
關(guān)注兩個(gè)函數(shù)gprof_callback和setup_signal。
啟動(dòng)程序,可以采用kill -s SIGUSR1 5722和kill -s SIGUSR2 5722來開始采集和停止采集,5722是進(jìn)程pid。
4、心得
最后,補(bǔ)充一點(diǎn),要用google-perftool來分析程序,必須保證程序能正常退出。
采用kcachegrind查看函數(shù)之間依賴,并分析程序性能