1、问题概述
之前的文章介绍过Asan来定位内存泄漏问题,虽然已经被集成到各大编译器中,但它的使用可能受到特定环境或配置的限制。例如,在某些复杂的系统或应用程序中,ASan可能会因为与程序的交互复杂性而遇到挑战,导致难以准确诊断问题。
本文介绍另一种内存泄漏的定位方法--Google Heap Profiler,大概的原理就是使用tcmalloc 来代替malloc calloc new等等,这样Google Heap Profiler就能知道内存的分配情况,从而分析出内存问题。
2、工具的获取与安装
Heap Profiler通常指对应用程序的堆分配进行收集或采样,来向我们报告程序的内存使用情况,以便分析内存占用原因或定位内存泄漏根源。
通常可使用的工具有google-perftools和google-gperftools,下载路径如下,本文主要使用gperftools进行问题的排查。
Perftools:https://code.google.com/p/google-perftools/
gperftools: http://code.google.com/p/gperftools/downloads/list
https://github.com/gperftools/gperftools(下载release版本)
通过交叉编译命令生成gperftools工具(tcmalloc库文件):
[root&user:]# ./configure --prefix=/gperftools/install/gperftools --target=arm-linux --host=arm-linux-gnueabihf --enable-frame-pointers
#--prefix=PREFIX //install architecture-independent files in PREFIX
#--host=HOST //cross-compile to build programs to run on HOST [BUILD]
#--enable-frame-pointers //Add -fno-omit-frame-pointer to compile flags
[root&user:]# make #编译源代码
[root&user:]# make install #将编译好的程序安装到系统中
编译成功后会生成tcmalloc的库,可以将静态库加入工程中进行链接,也可以直接将动态库直接导入设备中进行使用。
3、gperftools的使用
首先需要把tcmalloc链接到我们需要分析的程序中,为了简单起见,还是推荐链接这个libtcmalloc.a到自己的程序中。
[root&user:]# gcc -g main xxx.cpp -I./include -L ./lib -ltcmalloc -lpthread
链接之后,我们接下来的任务就是得到内存分析的dump文件,我们有两种方法:
3.1 动态dump方法
#include <gperftools/heap-profiler.h>
int main() {
HeapProfilerStart("memory-profiler"); //dump文件名称,会生成memory-profiler.0001.heap
// 你的代码
HeapProfilerDump("test");
HeapProfilerStop();
return 0;
}
在上面的例子中,我们使用 HeapProfilerStart 开启内存分析,执行我们需要测试的代码,然后使用 HeapProfilerStop 停止分析。
3.2 静态dump方法
#include <gperftools/heap-profiler.h>
int main() {
HeapProfilerDump(NULL);
// 你的代码
return 0;
}
在运行可执行程序之前,直接定义一个环境变量HEAPPROFILE来指定dumpprofile文件的位置,如:/tmp/test.log,它将会在/tmp/目录下生成很多类似/tmp/test.log.0003.heap文件名的文件; 同时也可以设置收集频率:如HEAP_PROFILE_TIME_INTERVAL=20,表示每隔20秒钟收集一次产品堆内存;注意:HEAP_PROFILE_TIME_INTERVAL等参数必须与可执行程序放在一起执行;
[root&user:]# HEAP_PROFILE_TIME_INTERVAL=20 HEAPPROFILE=/tmp/test.log ./main
除了上述两个选项外,还可以设置dump的内存大小:
HEAP_PROFILE_ALLOCATION_INTERVAL:程序内存每增长这一数值之后就dump 一次内存,默认是1G (1073741824);
HEAP_PROFILE_INUSE_INTERVAL:程序如果一次性分配内存超过这个数值dump 默认是100K。
3.3 gperftools工具分析CPU性能
gperftools 同样可以用来分析和优化CPU 的强大工具,具体做法是a、在应用程序中包含#include <gperftools/profiler.h>;b、使用 ProfilerStart开启CPU分析,执行我们需要测试的代码,然后使用 ProfilerStop停止分析;c、接时包含profiler库。
#include <gperftools/heap-profiler.h>
int main() {
ProfilerStart("cpu-profiler.prof");
// 你的代码
ProfilerStop();
return 0;
}
生成的分析文件可以使用 gperftools 提供的 pprof 工具来查看:
[root&user:]# pprof ./main ./cpu-profiler.prof –text
4、Dump文件的分析
Dump文件生成之后,接下来可以通过pprof工具来查看内存的分布情况,pprof的使用形式都是:
pprof --option [ --focus=<regexp> ] [ --ignore=<regexp> ] [--line or addresses or functions] 可执行文件路径 对应的profile路径
部分options的选项如下:
--text Generate text report
--callgrind Generate callgrindformat to stdout
--gv Generate Postscript and display
--evince Generate PDF and display
--web Generate SVG and display
--list=<regexp> Generate source listing ofmatching routines
--disasm=<regexp> Generate disassembly of matching routines
--symbols Printdemangled symbol names found at given addresses
--dot Generate DOT file to stdout
--ps Generate Postcript to stdout
--pdf Generate PDF to stdout
--svg Generate SVG to stdout
--gif Generate GIF to stdout
--raw Generate symbolized pprof data (useful with remote fetch)
--lines Aggregate at the source code line level.
--drop_negative Ignore negative differences
方括号中的项目是可选项目。<regexp>表示正则表达式
4.1 pprof工具的使用
正常情况下,可执行文件运行的设备环境中是不带pprof工具的,大概率是设备的性能不足,因此需要将设备端收集到设备堆内存信息文件(如profile.0001.heap),从设备端文件拷贝至编译服务器;同时需要将可执行程序依赖的动态库从设备端拷贝出来至编译服务器上。
1)单个版本内存情况:主要用于排查该堆文件收集时刻设备端堆内存申请情况
[root&user:]# ./pprof ./out_debug ./profile.0008.heap --pdf > 0008.pdf --lib_prefix=/debug /att-gperftools/lib
#./out_debug 可执行文件(携带调试信息,编译时加上-g)
#./profile.0008.heap dump文件
#--pdf > 0008.pdf 分析完成后的文件保存为0008.pdf
#--lib_prefix 指向工程中使用的动态库的路径
2)两个版本比对:主要用于排查前后两堆文件收集时刻之间,设备端堆内存的变化情况
[root&user:]# ./pprof ./out_debug --base=./profile.0005.heap ./profile.0012.heap --text > 0005-0012.txt --lib_prefix=/ debug/att-gperftools/lib
#--base=./profile.0005.heap 基准的dump文件为profile.0005.heap
[root&user:]# ./pprof ./out_debug --base=./profile.0005.heap ./profile.0012.heap --text > 0005-0012.txt --lib_prefix=/ debug/att-gperftools/lib --inuse_space --drop_negative --lines --show_bytes --heapcheck --edgefraction=1e-10 --nodefraction=1e-10
#--inuse_space 正在使用,尚未释放的空间
#--drop_negative 忽略两dump文件中没有交集的差异
#--lines 按照源代码的每一行来进行数据的汇总或分析
#--heapcheck 堆的检查
#--edgefraction 隐藏那些低于某个特定比例(由乘以总量得到)的边或连接
#--nodefraction 设置一个阈值,低于这个阈值的节点将被隐藏,以便于更清晰地展示重要的节点信息
针对于内存的分析,pprof在web 浏览器界面提供了几个维度去分析。
--inuse_space: 正在使用,尚未释放的空间
--inuse_object: 正在使用,尚未释放的对象
--alloc_space: 所有分配的空间,包含已释放的
--alloc_objects: 所有分配的对象,包含已释放的
拿inuse_object举例,性能剖析图是这样的,箭头显示了正在使用的对象个数,其中 main.allocMem函数自身有一个4MB多的对象,其子函数有4个对象:
4.2 堆文件解析
对于单个版本内存生成的pdf解读下图中;
1)Total MB:4.2,为hicore进程该时刻共申请4.2MB堆内存;
2)mem_test_task 中2.0(46.4%) of 3.9(92.9%),表示mem_test_task的函数中申请的堆内存为2.0MB占总的堆内存申请的46.4%;3.9-2.0=1.9MB,表示mem_test_task的子函数中申请的堆内存1.9MB;
5 后记
注:若仅收集的profile.XXXX.heap文件中有内存泄漏情况,但pprof工具无法还原其内存申请调用关系,则说明gperftools源码中寄存器的定义与硬件平台不一致,可查看当前gperftools工具版本是否支持使用的硬件平台,以arm为例:
gperftools 提供了主要包括 CPU 分析器和堆分析器。适合用来识别程序中的性能热点和内存泄漏。对于想要进行全面性能和资源优化的需求来说,可能还需要考虑一些其他工具和技术作为 gperftools 的互补。以下是对 gperftools 工具的一些补充:
1)Valgrind:
Valgrind 是一个编程工具套件,用于内存调试、内存泄漏检测以及性能分析。虽然 gperftools 的堆分析器能够帮助检测内存泄漏,Valgrind 的 Memcheck 工具在某些情况下可能提供更详细的内存访问和泄漏信息。
2)AddressSanitizer:
AddressSanitizer (ASan) 是一个快速的内存错误检测器,可以检测出各种内存访问错误。ASan 被集成在 LLVM/Clang 和 GCC 中,与 gperftools 相比,它在运行时插入的检查可以自动发现如使用后释放、堆栈缓冲区溢出等错误。