Linux效能評測工具之一:gprof篇
這些天自己試著對專案作一些壓力測試和效能優化,也對用過的測試工具作一些總結,並把相關的資料作一個彙總,以便以後信手拈來!
1 簡介
改進應用程式的效能是一項非常耗時耗力的工作,但是究竟程式中是哪些函式消耗掉了大部分執行時間,這通常都不是非常明顯的。GNU 編譯器工具包所提供了一種剖析工具 GNU profiler(gprof)。gprof 可以為 Linux平臺上的程式精確分析效能瓶頸。gprof精確地給出函式被呼叫的時間和次數,給出函式呼叫關係。
gprof 使用者手冊網站 http://sourceware.org/binutils/docs-2.17/gprof/index.html
2 功能
Gprof 是GNU gnu binutils工具之一,預設情況下linux系統當中都帶有這個工具。
1. 可以顯示“flat profile”,包括每個函式的呼叫次數,每個函式消耗的處理器時間,
2. 可以顯示“Call graph”,包括函式的呼叫關係,每個函式呼叫花費了多少時間。
3. 可以顯示“註釋的原始碼”--是程式原始碼的一個複本,標記有程式中每行程式碼的執行次數。
3 原理
通過在編譯和連結程式的時候(使用 -pg 編譯和連結選項),gcc 在你應用程式的每個函式中都加入了一個名為mcount ( or “_mcount” , or “__mcount” , 依賴於編譯器或作業系統)的函式,也就是說你的應用程式裡的每一個函式都會呼叫mcount, 而mcount 會在記憶體中儲存一張函式呼叫圖,並通過函式呼叫堆疊的形式查詢子函式和父函式的地址。這張呼叫圖也儲存了所有與函式相關的呼叫時間,呼叫次數等等的所有資訊。
4 使用流程
1. 在編譯和連結時 加上-pg選項。一般我們可以加在 makefile 中。
2. 執行編譯的二進位制程式。執行引數和方式同以前。
3. 在程式執行目錄下 生成 gmon.out 檔案。如果原來有gmon.out 檔案,將會被重寫。
4. 結束程式。這時 gmon.out 會再次被重新整理。
5. 用 gprof 工具分析 gmon.out 檔案。
5 引數說明
l -b 不再輸出統計圖表中每個欄位的詳細描述。
l -p 只輸出函式的呼叫圖(Call graph的那部分資訊)。
l -q 只輸出函式的時間消耗列表。
l -e Name 不再輸出函式Name 及其子函式的呼叫圖(除非它們有未被限制的其它父函式)。可以給定多個 -e 標誌。一個 -e 標誌只能指定一個函式。
l -E Name 不再輸出函式Name 及其子函式的呼叫圖,此標誌類似於 -e 標誌,但它在總時間和百分比時間的計算中排除了由函式Name 及其子函式所用的時間。
l -f Name 輸出函式Name 及其子函式的呼叫圖。可以指定多個 -f 標誌。一個 -f 標誌只能指定一個函式。
l -F Name 輸出函式Name 及其子函式的呼叫圖,它類似於 -f 標誌,但它在總時間和百分比時間計算中僅使用所列印的例程的時間。可以指定多個 -F 標誌。一個 -F 標誌只能指定一個函式。-F 標誌覆蓋 -E 標誌。
l -z 顯示使用次數為零的例程(按照呼叫計數和累積時間計算)。
一般用法: gprof –b 二進位制程式 gmon.out >report.txt
6 報告說明
Gprof 產生的資訊解釋:
%time |
Cumulative seconds |
Self Seconds |
Calls |
Self TS/call |
Total TS/call |
name |
該函式消耗時間佔程式所有時間百分比 |
程式的累積執行時間 (只是包括gprof能夠監控到的函式) |
該函式本身執行時間 (所有被呼叫次數的合共時間) |
函式被呼叫次數 |
函式平均執行時間 (不包括被呼叫時間) (函式的單次執行時間) |
函式平均執行時間 (包括被呼叫時間)
(函式的單次執行時間) |
函式名 |
Call Graph 的欄位含義:
Index |
%time |
Self |
Children |
Called |
Name |
索引值 |
函式消耗時間佔所有時間百分比 |
函式本身執行時間 |
執行子函式所用時間 |
被呼叫次數 |
函式名 |
注意:
程式的累積執行時間只是包括gprof能夠監控到的函式。工作在核心態的函式和沒有加-pg編譯的第三方庫函式是無法被gprof能夠監控到的,(如sleep()等)
Gprof 的具體引數可以 通過 man gprof 查詢。
7 共享庫的支援
對於程式碼剖析的支援是由編譯器增加的,因此如果希望從共享庫中獲得剖析資訊,就需要使用 -pg 來編譯這些庫。提供已經啟用程式碼剖析支援而編譯的 C 庫版本(libc_p.a)。
如果需要分析系統函式(如libc庫),可以用 –lc_p替換-lc。這樣程式會連結libc_p.so或libc_p.a。這非常重要,因為只有這樣才能監控到底層的c庫函式的執行時間,(例如memcpy(),memset(),sprintf()等)。
gcc example1.c –pg -lc_p -o example1
注意要用ldd ./example | grep libc來檢視程式連結的是libc.so還是libc_p.so
8 使用者時間與核心時間
gprof 的最大缺陷:它只能分析應用程式在執行過程中所消耗掉的使用者時間,無法得到程式核心空間的執行時間。通常來說,應用程式在執行時既要花費一些時間來執行使用者程式碼,也要花費一些時間來執行 “系統程式碼”,例如核心系統呼叫sleep()。
有一個方法可以檢視應用程式的執行時間組成,在 time 命令下面執行程式。這個命令會顯示一個應用程式的實際執行時間、使用者空間執行時間、核心空間執行時間。
如 time ./program
輸出:
real 2m30.295s
user 0m0.000s
sys 0m0.004s
9 注意事項
1. g++在編譯和連結兩個過程,都要使用-pg選項。
2. 只能使用靜態連線libc庫,否則在初始化*.so之前就呼叫profile程式碼會引起“segmentation fault”,解決辦法是編譯時加上-static-libgcc或-static。
3. 如果不用g++而使用ld直接連結程式,要加上鍊接檔案/lib/gcrt0.o,如ld -o myprog /lib/gcrt0.o myprog.o utils.o -lc_p。也可能是gcrt1.o
4. 要監控到第三方庫函式的執行時間,第三方庫也必須是新增 –pg 選項編譯的。
5. gprof只能分析應用程式所消耗掉的使用者時間.
6. 程式不能以demon方式執行。否則採集不到時間。(可採集到呼叫次數)
7. 首先使用 time 來執行程式從而判斷 gprof 是否能產生有用資訊是個好方法。
8. 如果 gprof 不適合您的剖析需要,那麼還有其他一些工具可以克服 gprof 部分缺陷,包括 OProfile 和 Sysprof。
9. gprof對於程式碼大部分是使用者空間的CPU密集型的程式用處明顯。對於大部分時間執行在核心空間或者由於外部因素(例如作業系統的 I/O 子系統過載)而執行得非常慢的程式難以進行優化。
10. gprof 不支援多執行緒應用,多執行緒下只能採集主執行緒效能資料。原因是gprof採用ITIMER_PROF訊號,在多執行緒內只有主執行緒才能響應該訊號。但是有一個簡單的方法可以解決這一問題:http://sam.zoy.org/writings/programming/gprof.html
11. gprof只能在程式正常結束退出之後才能生成報告(gmon.out)。
a) 原因: gprof通過在atexit()裡註冊了一個函式來產生結果資訊,任何非正常退出都不會執行atexit()的動作,所以不會產生gmon.out檔案。
b) 程式可從main函式中正常退出,或者通過系統呼叫exit()函式退出。
10 多執行緒應用
gprof 不支援多執行緒應用,多執行緒下只能採集主執行緒效能資料。原因是gprof採用ITIMER_PROF訊號,在多執行緒內只有主執行緒才能響應該訊號。
採用什麼方法才能夠分析所有執行緒呢?關鍵是能夠讓各個執行緒都響應ITIMER_PROF訊號。可以通過樁子函式來實現,重寫pthread_create函式。
//////////////////// gprof-helper.c////////////////////////////
#define _GNU_SOURCE
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <pthread.h>
static void * wrapper_routine(void *);
/* Original pthread function */
static int (*pthread_create_orig)(pthread_t *__restrict,
__const pthread_attr_t *__restrict,
void *(*)(void *),
void *__restrict) = NULL;
/* Library initialization function */
void wooinit(void) __attribute__((constructor));
void wooinit(void)
{
pthread_create_orig = dlsym(RTLD_NEXT, "pthread_create");
fprintf(stderr, "pthreads: using profiling hooks for gprof/n");
if(pthread_create_orig == NULL)
{
char *error = dlerror();
if(error == NULL)
{
error = "pthread_create is NULL";
}
fprintf(stderr, "%s/n", error);
exit(EXIT_FAILURE);
}
}
/* Our data structure passed to the wrapper */
typedef struct wrapper_s
{
void * (*start_routine)(void *);
void * arg;
pthread_mutex_t lock;
pthread_cond_t wait;
struct itimerval itimer;
} wrapper_t;
/* The wrapper function in charge for setting the itimer value */
static void * wrapper_routine(void * data)
{
/* Put user data in thread-local variables */
void * (*start_routine)(void *) = ((wrapper_t*)data)->;start_routine;
void * arg = ((wrapper_t*)data)->;arg;
/* Set the profile timer value */
setitimer(ITIMER_PROF, &((wrapper_t*)data)->;itimer, NULL);
/* Tell the calling thread that we don't need its data anymore */
pthread_mutex_lock(&((wrapper_t*)data)->;lock);
pthread_cond_signal(&((wrapper_t*)data)->;wait);
pthread_mutex_unlock(&((wrapper_t*)data)->;lock);
/* Call the real function */
return start_routine(arg);
}
/* Our wrapper function for the real pthread_create() */
int pthread_create(pthread_t *__restrict thread,
__const pthread_attr_t *__restrict attr,
void * (*start_routine)(void *),
void *__restrict arg)
{
wrapper_t wrapper_data;
int i_return;
/* Initialize the wrapper structure */
wrapper_data.start_routine = start_routine;
wrapper_data.arg = arg;
getitimer(ITIMER_PROF, &wrapper_data.itimer);
pthread_cond_init(&wrapper_data.wait, NULL);
pthread_mutex_init(&wrapper_data.lock, NULL);
pthread_mutex_lock(&wrapper_data.lock);
/* The real pthread_create call */
i_return = pthread_create_orig(thread,
attr,
&wrapper_routine,
&wrapper_data);
/* If the thread was successfully spawned, wait for the data
* to be released */
if(i_return == 0)
{
pthread_cond_wait(&wrapper_data.wait, &wrapper_data.lock);
}
pthread_mutex_unlock(&wrapper_data.lock);
pthread_mutex_destroy(&wrapper_data.lock);
pthread_cond_destroy(&wrapper_data.wait);
return i_return;
}
///////////////////
然後編譯成動態庫 gcc -shared -fPIC gprof-helper.c -o gprof-helper.so -lpthread -ldl
使用例子:
/////////////////////a.c/////////////////////////////
#include <stdio.h>;
#include <stdlib.h>;
#include <unistd.h>;
#include <pthread.h>;
#include <string.h>;
void fun1();
void fun2();
void* fun(void * argv);
int main()
{
int i =0;
int id;
pthread_t thread[100];
for(i =0 ;i< 100; i++)
{
id = pthread_create(&thread[i], NULL, fun, NULL);
printf("thread =%d/n",i);
}
printf("dsfsd/n");
return 0;
}
void* fun(void * argv)
{
fun1();
fun2();
return NULL;
}
void fun1()
{
int i = 0;
while(i<100)
{
i++;
printf("fun1/n");
}
}
void fun2()
{
int i = 0;
int b;
while(i<50)
{
i++;
printf("fun2/n");
//b+=i;
}
}
///////////////
gcc -pg a.c gprof-helper.so
執行程式:
./a.out
分析gmon.out:
gprof -b a.out gmon.out
相關文章
- Linux C++程式進行效能分析工具gprof使用入門LinuxC++
- linux 效能分析工具dstat之一Linux
- Linux效能評估工具Linux
- linux高階工具命令(三)使用gprof和oprofile查詢效能瓶頸Linux
- Linux C++ 開發9 - 手把手教你使用gprof效能分析工具LinuxC++
- 谷歌效能測評工具lighthouse使用谷歌
- gprof的效能優化實踐優化
- Android App效能評測分析-網路流量篇AndroidAPP
- Linux命令列效能檢測工具Linux命令列
- 幾個可以來做cassandra效能評測的工具
- 效能自動化測試工具Loadrunner篇
- 效能測試總結(三)--工具選型篇
- 效能測試工具
- Linux iperf 網路傳輸效能測試工具Linux
- 深入淺出開源效能測試工具 Locust (使用篇 2)
- 一篇文章帶你掌握效能測試工具——JmeterJMeter
- 深入淺出開源效能測試工具 Locust (使用篇 1)
- 調諧LINUX網路效能之除錯工具篇(轉)Linux除錯
- 資訊系統效能評測
- 安全測評基礎-安全測評常用測試工具講解
- 企業文化測評工具
- 效能測試工具Locust
- mysqlslap 效能測試工具MySql
- 效能測試工具supersmackMac
- 效能壓測工具 —— wrk
- 效能測試工具 - Siege
- 效能測試專案篇
- Linux效能分析工具Linux
- 測試開發之效能篇-效能測試設計
- Linux下C開發工具介紹 GCC gdb xxgdb calls calltree cproto indent gprof (轉)LinuxGC
- 效能優化篇 - Performance(工具 & api)優化ORMAPI
- 移動端orm框架效能測評ORM框架
- IBM Lotus Domino 8.5 效能評測IBM
- 四款iOS加固工具評測iOS
- AIGC工具的使用測評AIGC
- Linux伺服器效能評估Linux伺服器
- 一個好用的線上測評工具——線上測評H5H5
- ABAP Webdynpro效能測試工具Web