**
gperftools 使用说明
**
1.实现原理
gperftools 基于采样技术来分析应用的性能;这种技术的基本思想是在程序运行过程中以固定的时间间隔,记录当前的调用栈。通过收集大量的样本,我们就可以得到程序在各个函数中花费的时间的大致比例。
- 调用栈的信息:调用栈可以告诉我们程序在某一时刻正在执行哪个函数,以及这个函数是由哪个函数调用的。这样我们就可以知道程序的执行流程。
- 统计信息:通过在多个时间点收集调用栈信息,我们可以统计每个函数被调用的次数,将每个函数出现在栈顶的次数除以总的样本数,得到这个函数占用cpu时间的比例;这个比例被认为函数平均执行时间的估计。
- 性能瓶颈:如果一个函数在调用栈中出现的次数特别多,那么这个函数可能就是性能瓶颈。我们可以对这个函数进行优化,以提高程序的整体性能。
2.使用方法-采样
在应用链接libprofiler.so 后(代码里要调用以下profiler里的一个接口,否则libprofiler 会被忽略), 默认不会启动profile 采样,可以通过以下方法控制启停:
2.1 使用API控制
在应用代码里主动的调用start /stop 函数:
/* Start profiling and write profile info into fname, discarding any
- existing profiling data in that file.
- This is equivalent to calling ProfilerStartWithOptions(fname, NULL).
/
PERFTOOLS_DLL_DECL int ProfilerStart(const char fname);
/* Stop profiling. Can be started again with ProfilerStart(), but
- the currently accumulated profiling data will be cleared.
*/
PERFTOOLS_DLL_DECL void ProfilerStop(void);
2.2 使用环境变量控制
export CPUPROFILE=“/work/qnx/cpuprof”
export CPUPROFILESIGNAL=“12”
#运行
./build/cpuprof
#启动profile
kill -n 12 $(pidof cpuprof)
#停止profile
kill -n 12 $(pidof cpuprof)
2.3 对线程profile
对线程profile 需要同时执行以下操作:
-
设置环境变量export CPUPROFILE_PER_THREAD_TIMERS=“1”
-
确保timer_create函数在程序里被调用过
-
在目标线程的入口调用ProfileHandlerRegisterThread()函数
2.4 注意事项 -
Profiler 默认使用的是普通定时器;若是有线程屏蔽了定时器信号,这个线程就不能被采样
-
start/stop之间的间隔越长,收集到的数据越多, 分析结果越准确
3.profile分析
对xx采样,获取数据 cpuprof.0-0619。 可以通过以下两种方式,查看主要耗时函数
3.1 命令行方式
命令方式简单直观,能够快速看到热点函数:
root@a1000:/userdata# ./pprof ./xx/opt/xx/bin/xx /work/qnx/cpuprof.0-0619
File: xx
Type: cpu
Entering interactive mode (type “help” for commands, “o” for options)
(pprof) top
Showing nodes accounting for 124.15s, 34.39% of 361.04s total
Dropped 2434 nodes (cum <= 1.81s)
Showing top 10 nodes out of 206
flat flat% sum% cum cum%
49.54s 13.72% 13.72% 49.54s 13.72% _IO_str_seekoff
15.50s 4.29% 18.01% 15.50s 4.29% nanosleep
15.39s 4.26% 22.28% 48.92s 13.55% malloc
8.54s 2.37% 24.64% 8.54s 2.37% google::protobuf::io::CodedOutputStream::WriteVarint32ToArray
7.48s 2.07% 26.71% 8.04s 2.23% pthread_cond_timedwait
6.62s 1.83% 28.55% 68.76s 19.04% google::protobuf::Arena::CreateMaybeMessage
5.52s 1.53% 30.08% 11.32s 3.14% apserial::ref_float32_t::_InternalSerialize
5.46s 1.51% 31.59% 14.34s 3.97% apserial::ref_float32_t::ByteSizeLong
5.24s 1.45% 33.04% 5.24s 1.45% free
4.86s 1.35% 34.39% 4.86s 1.35% google::protobuf::internal::InternalMetadataWithArenaBase::~InternalMetadataWithArenaBase
3.2 web页面
Web 页面可以提供非常丰富的信息,依据调用链,它可以直观的展示热点函数的主要执行路径。
-
构造二进制目录
-
将执行程序和相关依赖库按照板端的路径打包
-
将二进制包放到WSL或者其他Linux虚拟机里
-
执行分析指令
root@J2P14s0187:/mnt/d/debuglogs/cpuprof# export PPROF_BINARY_PATH=/mnt/d/debuglogs/cpuprof/xx
root@J2P14s0187:/mnt/d/debuglogs/cpuprof# ./pprof -http 0.0.0.0:8001 -no_browser ./xx/userdata/xx/opt/APMonitor/bin/xx. /apmonitor/cpuprof.0-0619
Serving web UI on http://0.0.0.0:8001 -
从浏览器查看
在出现Serving web UI on http://0.0.0.0:8001 后, 从浏览器登陆8001端口查看:
4.实现分析
4.1 定时器
4.1.1 线程定时器和普通定时器
定时器分为线程定时器和普通定时器两种,主要区别在于它们发送信号的方式和目标。
- 线程定时器:当线程定时器到期时,它会向创建它的线程发送信号。这意味着,即使在多线程环境中,也可以确保信号被正确地发送到了预期的线程。这在需要对特定线程进行精确计时或调度的情况下非常有用。
- 普通定时器:当普通定时器到期时,它会向进程发送信号。在多线程环境中,这可能会导致信号被任何一个线程接收,这取决于操作系统的调度策略。因此,普通定时器更适合于对整个进程进行计时或调度。
默认情况下,ProfileHandle 使用普通定时器,只有同时满足以下条件才会使用线程定时器: - gperftools编译时开启线程定时器支持(默认开启)
- 指定了环境变量“CPUPROFILE_PER_THREAD_TIMERS”或“CPUPROFILE_TIMER_SIGNAL”
- 若链接技术找到了timer_create的实现
4.1.2 定时器类型
setitimer 是一个在 Unix-like 系统(如 Linux)中用于设置一个间隔定时器的系统调用。当定时器到期时,系统将向进程发送一个信号。
setitimer 函数的原型如下:
#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
这个函数接收三个参数:
-
which:这个参数指定了定时器的类型,可以是以下三种之一:
- ITIMER_REAL:当此类型的定时器到期时,系统将向进程发送 SIGALRM 信号。这个定时器以实际的时间(或者说“墙钟时间”)来计算。
- ITIMER_VIRTUAL:当此类型的定时器到期时,系统将向进程发送 SIGVTALRM 信号。这个定时器只在进程在用户态执行时才计时。
- ITIMER_PROF:当此类型的定时器到期时,系统将向进程发送 SIGPROF 信号。这个定时器在进程在用户态执行和系统态执行时都会计时。 -
new_value:这是一个指向 itimerval 结构体的指针,该结构体指定了定时器的新值。itimerval 结构体定义如下:
struct itimerval {
struct timeval it_interval; /* next value /
struct timeval it_value; / current value */
};
其中,it_interval 是定时器到期后的新值,it_value 是定时器的当前值。如果 it_value 不为零,那么定时器将在 it_value 指定的时间后到期;如果 it_value 为零,那么定时器将停止。如果 it_interval 不为零,那么定时器将在每次到期后自动重置为 it_interval 指定的值。
3. old_value:这是一个指向 itimerval 结构体的指针,如果不为 NULL,那么系统将在这里存储定时器的旧值。
3.2 采样
注册定时器的信号处理函数,在定时器处理函数里,记录线程的调用栈。