一、gprof介绍
二、Gprof功能:
三、Gprof 实现原理:
四、gprof 的适用范围
五、gprof的安装使用
参考资料:
六、Gprof基本用法:
1.使用-pg选项编译和链接你的应用程序。
2.执行你的应用程序,使之运行完成后生成供gprof分析的数据文件(默认是gmon.out)。
3.使用gprof程序分析你的应用程序生成的数据,例如:gprof a.out gmon.out。
举例
gcc-Wall
4 gprof输出分析
在gmon.out文件产生之后,可以通过GNU binutils中提供的工具gprof来分析数据,转换成容易阅读、理解的格式。
一般用法:
# gprof Binary-file gmon.out >report.txt
其中,Binary-file指的是所运行的程序(也可以是程序调用到的库文件),gmon.out就是前面所输出的那个文件,report.txt就是生成的分析报告了。Gprof提供了丰富的参数选项,以控制报告输出的内容。
多进程
如果用gprof分析多进程程序,则可能一个进程的gmon.out覆盖另一个进程的gmon.out,解决方法是在执行程序之前执行:export GMON_OUT_PREFIX=x.out则之后生成的文件名就如x.out.pid,多进程的gmon.out就不会相互覆盖。
多线程
gprof无法分析多线程程序。缘故是gprof使用ITIMER_PROF定时器,当超时时由内核向应用程序发送信号。但多线程程序只有主线程接收ITIMER_PROF。这里有一个简单的实现方法:对pthread_create进行包装,并以动态库的形式在程序运行前加载。我通过上文的描述,整理了一个gprof分析多线程程序的程序,可供参考。
$ gcc -shared -fPIC gprof_helper.c -o ghelper2.so -lpthread -ldl # create ghelp2.so $ gcc test.c -lpthread $ ./a.out hello gprof # output $ LD_PRELOAD=./ghelper2.so ./a.out pthread: using profiling hooks for gprof # output hello gprof # output # no time accumulated Each sample counts as 0.01 seconds. no time accumulated
gprof的输出表明没有时间被统计到。虽然函数调用次数是统计正确的,但没有时间(调用次数多未必就最耗时)。gprof显示每0.01秒采样一次,如果函数执行的时间都非常短,例如低于0.01秒,则统计不到任何时间。
综述
- gprof用于分析函数调用耗时,可用之抓出最耗时的函数,以便优化程序。
- gcc链接时也一定要加-pg参数,以使程序运行结束后生成gmon.out文件,供gprof分析。
- gprof默认不支持多线程程序,默认不支持共享库程序。
- gcc -pg 编译程序
- 运行程序,程序退出时生成 gmon.out
- gprof ./prog gmon.out -b 查看输出
注意事项
- 程序如果不是从main return或exit()退出,则可能不生成gmon.out。
- 程序如果崩溃,可能不生成gmon.out。
- 测试发现在虚拟机上运行,可能不生成gmon.out。
- 一定不能捕获、忽略SIGPROF信号。man手册对SIGPROF的解释是:profiling timer expired. 如果忽略这个信号,gprof的输出则是:Each sample counts as 0.01 seconds. no time accumulated.
- 如果程序运行时间非常短,则gprof可能无效。因为受到启动、初始化、退出等函数运行时间的影响。
- 程序忽略SIGPROF信号!
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 查詢。
gprof 的最大缺陷:它只能分析應用程序在運行過程中所消耗掉的用戶時間,無法得到程序內核空間的運行時間。通常來說,應用程序在運行時既要花費一些時間來運行用戶代碼,也要花費一些時間來運行 「系統代碼」,例如內核系統調用sleep()。
有一個方法可以查看應用程序的運行時間組成,在 time 命令下面執行程序。這個命令會顯示一個應用程序的實際運行時間、用戶空間運行時間、內核空間運行時間。
如 time ./program
輸出:
real 2m30.295s
user 0m0.000s
sys 0m0.004s
gprof只能分析應用程序所消耗掉的用戶時間.
1)一般gprof只能查看用户函数信息。如果想查看库函 数的信息,需要在编译是再加入“-lc_p”编 译参数代替“-lc”编译参数,这样程序会链接libc_p.a库, 才可以产生库函数的profiling信息。
2) gprof只能在程序正常结束退出之后才 能生成程序测评报告,原因是gprof通过在atexit()里 注册了一个函数来产生结果信息,任何非正常退出都不会执行atexit()的动作,所以不会产生gmon.out文件。如果你的程序是一个不会退出的服务程序,那就只有修改代码来达到目的。如果不想改变程 序的运行方式,可以添加一个信号处理函数解决问题(这样对代码修改最少),例如:
static void sighandler( int sig_no )
{
exit(0);
}
signal( SIGUSR1, sighandler );
当使用kill -USR1 pid 后,程序退出,生成gmon.out文件。
HOWTO: using gprof
with multithreaded applications
http://sam.zoy.org/writings/programming/gprof.html
/* gprof-helper.c -- preload library to profile pthread-enabled programs
*
* Authors: Sam Hocevar <sam at zoy dot org>
* Daniel Jönsson <danieljo at fagotten dot org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Do What The Fuck You Want To
* Public License as published by Banlu Kemiyatorn. See
* http://sam.zoy.org/projects/COPYING.WTFPL for more details.
*
* Compilation example:
* gcc -shared -fPIC gprof-helper.c -o gprof-helper.so -lpthread -ldl
*
* Usage example:
* LD_PRELOAD=./gprof-helper.so your_program
*/
#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;
}