http://blog.chinaunix.net/uid-25194149-id-3215487.html
#不知道這是在哪裏找的了,感謝各位~
性能分析工具gprof介紹
Ver:1.0
目錄
1. GPROF介紹 4
2. 使用步驟 4
3. 使用舉例 4
3.1 測試環境 4
3.2 測試代碼 4
3.3 數據分析 5
3.3.1 flat profile模式 6
3.3.2 call graph模式 7
4. 鏈接庫中的函數 7
5. 使用侷限 8
6. 分析示例 12
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1. gprof介紹
gprof是GNU profile工具,可以運行於linux、AIX、Sun等操作系統進行C、C++、Pascal、Fortran程序的性能分析,用於程序的性能優化以及程序瓶頸問題的查找和解決。通過分
析應用程序運行時產生的“flat profile”,可以得到每個函數的調用次數,每個函數消耗的處理器時間,也可以得到函數的“調用關係圖”,包括函數調用的層次關係,每個函
數調用花費了多少時間。
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2. 使用步驟
1) 用gcc、g++、xlC編譯程序時,使用-pg參數,如:g++ -pg
-o test.exe test.cpp
編譯器會自動在目標代碼中插入用於性能測試的代碼片斷,這些代碼在程序運行時採集並記錄函數的調用關係和調用次數,並記錄函數自身執行時間和被調用函數的執行時間。
2) 執行編譯後的可執行程序,如:./test.exe。該步驟運行程序的時間會稍慢於正常編譯的可執行程序的運行時間。程序運行結束後,會在程序所在路徑下生成一個缺省文
件名爲gmon.out的文件,這個文件就是記錄程序運行的性能、調用關係、調用次數等信息的數據文件。
3) 使用gprof命令來分析記錄程序運行信息的gmon.out文件,如:gprof test.exe gmon.out則可以在顯示器上看到函數調用相關的統計、分析信息。上述信息也可以採用
gprof test.exe gmon.out> gprofresult.txt重定向到文本文件以便於後續分析。
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3. 使用舉例
3.1 測試環境
本文提供的樣例的測試環境如下:
? Linux server164 2.6.9-22.ELsmp #1 SMP Mon Sep 19 18:32:14 EDT 2005 i686 i686 i386 GNU/Linux
? gcc version 3.2.3 20030502 (Red Hat Linux 3.2.3-47.3)
? GNU gprof 2.15.92.0.2
3.2 測試代碼
清單 1. 耗時測試應用程序示例
example1.c
#include <stdio.h>
int a(void)
{
int i=0,g=0;
while(i++<100000)
{
g+=i;
}
return g;
}
int b(void)
{
int i=0,g=0;
while(i++<400000)
{
g +=i;
}
return g;
}
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();
}
}
這個應用程序包括兩個函數:a 和 b,它們通過運行不同次數的循環來消耗不同的CPU時間。main 函數中採用了一個循環來反覆調用這兩個函數。函數b中循環的次數是 a 函數的
4 倍,因此我們期望通過gprof的分析結果可以觀察到大概有 20% 的時間花在了 a 函數中,而 80% 的時間花在了 b 函數中。
3.3 數據分析
在 gcc 編譯命令中加上 –pg參數即可。編譯方法如下:
gcc example1.c -pg -o example1 -O2 -lc
在編譯好這個應用程序之後,按照普通方式運行這個程序:
./example1 50000
程序運行完之後,應該會看到在當前目錄中新創建了一個文件 gmon.out。
3.3.1 flat profile模式
使用 gprof 命令分析gmon.out 文件,如下所示:
gprof example1 gmon.out -p
-p參數標識“flat profile”模式,在分析結果中不顯示函數的調用關係,AIX平臺默認此參數有效。
輸出以下內容:
清單 2. flat profile 的結果
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
80.38 203.27 203.27 50000 4.07 4.07 b
19.61 252.87 49.60 50000 0.99 0.99 a
0.00 252.88 0.01 main
上面結果中各個列的含義如下:
%time 函數以及衍生函數(函數內部再次調用的子函數)所佔的總運行時間的百分比
cumulative seconds 函數累計執行的時間
self seconds 函數執行佔用的時間
calls 函數的調用次數
self ms/call 每一次調用函數花費的時間microseconds,不包括衍生函數的運行時間
total ms/call 每一次調用函數花費的時間microseconds,包括衍生函數的運行時間
name 函數名稱
列的含義,在gprof的輸出結果中都有詳細的說明。
從輸出結果中可以看到,正如我們期望的一樣,b 函數所花費的時間大概是 a 函數所花費的時間的 4倍。
很多函數調用(例如 printf)在上面的輸出結果中都沒有出現。這是因爲大部分函數都是在C鏈接庫(libc.so)中,而鏈接庫並沒有使用 -pg 進行編譯,因此就沒有對鏈接庫中
的函數收集調度信息。
3.3.2 call graph模式
如果希望反映函數之間的調用關係,需要採用如下命令:
gprof example1 gmon.out –q
-q參數標識“call graph”模式,在分析結果中顯示函數的調用關係。
輸出以下內容:
清單 3. Call graph
granularity: each sample hit covers 4 byte(s) for 0.00% of 252.72 seconds
index % time self children called name
<spontaneous>
[1] 100.0 0.00 252.72 main [1]
201.41 0.00 50000/50000 b [2]
51.31 0.00 50000/50000 a [3]
-----------------------------------------------
201.41 0.00 50000/50000 main [1]
[2] 79.7 201.41 0.00 50000 b [2]
-----------------------------------------------
51.31 0.00 50000/50000 main [1]
[3] 20.3 51.31 0.00 50000 a [3]
-----------------------------------------------
上面結果中各個列的含義如下:
index 每個函數第一次出現時都分配了一個編號,根據編號可以方便的查找函數的具體分析數據
%time 函數以及衍生函數(函數內部再次調用的子函數)所佔的總運行時間的百分比
self 函數的總運行時間
children 衍生函數執行的總時間
called 函數被調用的次數,不包括遞歸調用
name 函數名稱
在name列中,可以看出函數之間的調用關係,main函數調用a、b函數,b函數被main函數調用,a函數被main函數調用。通過函數名稱後面的數字來標識這個文件中的函數,從而可
以快速定位函數的分析數據的位置,經過一層層的逐步深入的分析就得到各個函數的調用關係以及各個函數的性能數據。
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4. 鏈接庫中的函數
正如在前面曾經介紹的,對於代碼剖析的支持是由編譯器增加的,因此如果希望從鏈接庫(比如libc.a)中獲得剖析信息,就需要使用 -pg 來編譯這些庫。很多操作系統默認提供
了已經啓用代碼剖析支持而編譯的鏈接庫版本,例如:libc.a對應的採用-pg編譯的文件爲libc_p.a。對於沒有按照標準提供類似libc_p.a鏈接庫的操作系統版本來說,就需要安裝
已經編譯好的啓用代碼剖析的鏈接庫版本或者自己下載鏈接庫的源代碼進行編譯。
使用“啓用代碼剖析的鏈接庫版本”的應用場景,舉例如下:
gcc example1.c -g -pg -o example1 -O2 -lc_p
然後,像普通情況下一樣運行gprof對gmon.out進行分析,可以獲得 flat profile 或 call graph,而此時的分析結果會非常豐富,包含很多C的標準庫函數的調用信息,例如:
printf、write等。
注意:上例的libc_p.a是靜態鏈接庫。gprof目前還不支持對動態鏈接庫中的函數進行性能分析。
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5. 使用侷限
gprof的特點是它只能分析應用程序在運行過程中所消耗掉的CPU時間,只有當應用程序的函數消耗CPU的時候,gprof才能夠獲取函數的性能數據。如果應用程序在運行過程中暫時
掛起,並在系統內核喚醒應用程序後進一步執行,那麼在應用程序中間暫停的時間性能數據是無法統計的;而且在應用程序等待I/O操作返回的時間,性能數據也是無法統計的。
如果對清單 1 稍加修改,就可以清楚地看出這個問題:
清單 5. 爲清單 1 添加sleep()函數調用
example2.c:
#include <stdio.h>
int a(void)
{
sleep(1); /*調用系統函數進行sleep*/
return 0;
}
int b(void)
{
sleep(4); /*調用系統函數進行sleep*/
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();
}
}
現在 a 函數和 b 函數不再處理繁忙的循環了,而是分別調用sleep()來掛起1秒和4秒。
使用“啓用代碼剖析的鏈接庫版本”,編譯這個應用程序:
gcc example2.c -g -pg -o example2 -O2 -lc_p
並讓這個應用程序循環 30 次:
./example2 30
執行gprof example2 gmon.out –p所生成的結果如下:
清單 6. flat profile 顯示了系統調用的結果
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 21 0.00 0.00 _IO_file_overflow
0.00 0.00 0.00 13 0.00 0.00 strncmp
0.00 0.00 0.00 8 0.00 0.00 memset
0.00 0.00 0.00 8 0.00 0.00 sigprocmask
0.00 0.00 0.00 7 0.00 0.00 getenv
0.00 0.00 0.00 6 0.00 0.00 memcpy
0.00 0.00 0.00 5 0.00 0.00 _int_malloc
0.00 0.00 0.00 5 0.00 0.00 malloc
0.00 0.00 0.00 5 0.00 0.00 sigaction
0.00 0.00 0.00 5 0.00 0.00 strsep
0.00 0.00 0.00 4 0.00 0.00 nanosleep
0.00 0.00 0.00 4 0.00 0.00 sleep
0.00 0.00 0.00 4 0.00 0.00 strpbrk
0.00 0.00 0.00 3 0.00 0.00 _IO_new_file_xsputn
0.00 0.00 0.00 3 0.00 0.00 ____strtoul_l_internal
0.00 0.00 0.00 3 0.00 0.00 __cxa_atexit
0.00 0.00 0.00 3 0.00 0.00 __strtoul_internal
0.00 0.00 0.00 2 0.00 0.00 __errno_location
0.00 0.00 0.00 2 0.00 0.00 __find_specmb
0.00 0.00 0.00 2 0.00 0.00 a
0.00 0.00 0.00 2 0.00 0.00 b
0.00 0.00 0.00 2 0.00 0.00 mempcpy
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 __init_misc
0.00 0.00 0.00 1 0.00 0.00 __libc_csu_fini
0.00 0.00 0.00 1 0.00 0.00 __libc_csu_init
0.00 0.00 0.00 1 0.00 0.00 __libc_init_first
0.00 0.00 0.00 1 0.00 0.00 __libc_init_secure
0.00 0.00 0.00 1 0.00 0.00 __libc_setup_tls
0.00 0.00 0.00 1 0.00 0.00 __libc_start_main
0.00 0.00 0.00 1 0.00 0.00 __pthread_initialize_minimal
0.00 0.00 0.00 1 0.00 0.00 __setfpucw
0.00 0.00 0.00 1 0.00 0.00 __strtol_internal
0.00 0.00 0.00 1 0.00 0.00 _dl_aux_init
0.00 0.00 0.00 1 0.00 0.00 _dl_important_hwcaps
0.00 0.00 0.00 1 0.00 0.00 _dl_init_paths
0.00 0.00 0.00 1 0.00 0.00 _dl_non_dynamic_init
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 fillin_rpath
0.00 0.00 0.00 1 0.00 0.00 fini
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 printf
0.00 0.00 0.00 1 0.00 0.00 setitimer
0.00 0.00 0.00 1 0.00 0.00 strrchr
0.00 0.00 0.00 1 0.00 0.00 uname
0.00 0.00 0.00 1 0.00 0.00 vfprintf
0.00 0.00 0.00 1 0.00 0.00 write
對以上輸出結果進行分析可見,儘管 profile 已經記錄了每個函數被調用的確切次數,但是爲這些函數記錄的時間(實際上是所有函數)都是 0.00。這是因爲 sleep 函數實際上
是執行了一次對內核空間的調用,從而將應用程序的執行掛起了,然後有效地暫停執行,並等待內核再次將其喚醒。由於花在用戶空間執行的時間與花在內核中睡眠的時間相比非
常小,因此就被取整成零了。其原因是 gprof 以固定的週期對程序運行時間進行採樣測量,當程序掛起時,gprof就不會對程序進行採樣測量。
Gprof的特性使得有些程序非常難以進行優化,例如花費大部分時間睡眠等待內核喚醒的程序,或者由於外部因素(例如操作系統的 I/O 子系統過載)而運行得非常慢的程序。
通常,有一個很好的基準測試可以用來查看 gprof 對於應用程序的優化能起多大作用,方法是在 time 命令下面執行應用程序。此命令會顯示一個應用程序運行完成需要多少時間
,並且在用戶空間和內核空間各花費了多少時間。
例如:time ./example2 30
輸出結果如下所示:
清單 7. time 命令的輸出結果
No of iterations = 30
real 2m30.295s
user 0m0.000s
sys 0m0.004s
我們可以看出應用程序整體運行150秒左右,但大部分時間在睡眠狀態,幾乎沒有多少時間被花費在執行用戶空間和內核空間的代碼上,此時gprof的分析結果無法體現函數的實際
運行時間。
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6. 分析示例
在新疆聯通進行綜合入庫程序上線前的測試過程中,測試人員發現程序運行效率低下。下面以整個調優的過程來舉例說明基於gprof的性能分析方法。
性能分析前的準備階段,性能分析數據的生成步驟如下:
1) 首先更改makefile文件中的CC=g++ -g –pg 行,添加-pg參數。
2) 執行make clean刪除所有的目標文件、可執行文件
3) 重新編譯
4) 在命令行執行可執行程序,並通過控制檯發送啓動命令
5) 程序退出後在fileindb執行程序目錄下生成gmon.out(不是控制檯目錄)
6) 執行gprof fileindb > gsmindb_mon_result.txt把分析結果重定向到gsmindb_mon_result.txt文件
下面就是從gsmindb_mon_result.txt文件中截取的部分內容:
打開gsmindb_mon_result.txt文件後,可以看到127行:
[3] 37.2 0.00 41.90 1 .main [3]
0.00 41.88 1/1 .CMainManager::Run(int&) [4]
可見main函數中的CMainManager::Run運行時間(41.88)幾乎佔據了所有的運行時間(41.90),再查CMainManager::Run的分析數據在148行:
[4] 37.2 0.00 41.88 1 .CMainManager::Run(int&) [4]
0.00 41.81 1/1 .CBaseBillFile::DoEveryBill() [5]
可見CMainManager::Run 函數中CBaseBillFile::DoEveryBill執行佔了幾乎所有的時間,對於佔用時間比重很小的函數可以忽略。採用上面的方法一步一步向下查找,最終確定289
行:
0.56 11.98 849922/849922 .CMobileFavour::CheckAllCondition(int,int) [11]
[12] 11.1 0.56 11.98 849922 .CGsmFavourCheck::CheckAllCondition() [12]
0.06 5.82 849922/849922 .CGsmFavourCheck::CheckCallKind() [14]
在CGsmFavourCheck::CheckAllCondition()所調用的函數中CGsmFavourCheck::CheckCallKind()佔用時間的比重過大,幾乎達到50%;CGsmFavourCheck::CheckAllCondition總共用
時11.98,而CGsmFavourCheck::CheckCallKind()函數就佔用了5.82,從而說明CGsmFavourCheck::CheckCallKind()函數需要作進一步優化,以提高整體的運行效率。