背景
当程序长时间允许时(压测、服务器程序),就会面临更大的挑战,其中内存泄漏就是一类典型的问题,内存泄漏往往不易发现,导致的现象更是千奇百怪,本文主要介绍如何借助gperftools分析一个模块的内存泄漏
案例代码
#include <iostream>
#include <thread>
#include <cstring>
#include <chrono>
constexpr int kMallocSize = 1024*1024; // 1Mb
void func1() {
void* p = malloc(kMallocSize);
memset(p, 1, kMallocSize);
free(p);
}
void func2() {
void* p = malloc(kMallocSize);
memset(p, 1, kMallocSize);
//free(p);
}
int main() {
std::thread t1([](){
while(true) {
func1();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
});
std::thread t2([](){
while(true) {
func2();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
});
if(t1.joinable()) {
t1.join();
}
if(t2.joinable()) {
t2.join();
}
return 0;
}
很简单的一个程序,有两个线程每个1s执行一次任务,这个任务中会创建1M的内存,在线程2的任务中忘记释放了,这样这个程序长时间运行就会产生内存泄漏。
效果:每1s内存增加1M(观察下面top的RES列内容)
mem_leak内存增长
实际情况中,内存泄漏往往会藏在某个角落,很难通过阅读代码发现,特别是现在的项目代码量都很大
使用gperftools分析定位
gperftools介绍及安装
gperftools 是一组性能分析和内存优化工具,集合中最为人们所知的可能是它的 CPU 分析器(Profiler)和堆分析器(Heap Profiler)。下面我将介绍如何使用 gperftools 中的 CPU 分析器来对 C/C++ 程序进行性能分析。
安装 gperftools,你可以根据你所使用的操作系统和包管理器选择不同的方法。以下是在一些常见环境中安装 gperftools 的指南:
在 Ubuntu/Debian 系统上
使用 apt 包管理器安装:
sudo apt update
sudo apt install google-perftools libgoogle-perftools-dev
这将会安装 gperftools 及其开发库,如果你想要链接 CPU 分析器(profiler)到你的应用程序中,通常需要这些开发库。
在 CentOS/RHEL 系统上
使用 yum 包管理器安装:
sudo yum install gperftools gperftools-libs gperftools-devel
或者,如果你在使用新版本的 RHEL/CentOS(例如 RHEL 8 或 CentOS Stream),你可能需要使用 dnf:
sudo dnf install gperftools gperftools-libs gperftools-devel
在 Fedora 系统上
使用 dnf 包管理器安装:
sudo dnf install gperftools gperftools-libs gperftools-devel
在 macOS 上
如果你在 macOS 中,可以使用 Homebrew 来安装 gperftools:
brew install gperftools
从源代码编译安装
如果你的系统上没有预打包的 gperftools 版本,或者你需要一个特定版本的 gperftools,你还可以从源代码编译安装。首先,你需要下载最新版的源代码:
wget https://github.com/gperftools/gperftools/releases/download/gperftools-2.9.1/gperftools-2.9.1.tar.gz
tar -xzf gperftools-2.9.1.tar.gz
cd gperftools-2.9.1
然后编译并安装:
./configure
make
sudo make install
注意,编译 gperftools 可能需要额外的依赖项,如 libunwind、autoconf、automake 和 libtool 等。
使用堆分析器来分析内存分配情况
详细使用方法可以参考github说明文档
- 准备
pprof是否安装成功
[root@yms:/mnt/data/yms/study]pprof --version
pprof (part of gperftools 2.0)
Copyright 1998-2007 Google Inc.
This is BSD licensed software; see the source for copying conditions
and license information.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
tcmalloc.so的位置
[root@yms:/mnt/data/yms/study]find /usr/ -name libtcmalloc.so
/usr/lib/x86_64-linux-gnu/libtcmalloc.so
- 启动堆分析器
设置LD_PELOAD和HEAPPROFILE,然后运行程序
LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libtcmalloc.so" HEAPPROFILE=/tmp/mem_leak ./gperftools_demo/mem_leak
注意LD_PRELOAD是必须的
其他控制配置:
配置 | 作用 |
---|---|
LD_PRELOAD | 指定用于libtcmalloc.so的路径 |
HEAPPROFILE | 指定生成heap-profiling文件的路径 |
HEAP_PROFILE_ALLOCATION_INTERVAL | 指示heap dump大小,单位Byte,即每次申请达到N Byte后dump一次,default: 1073741824(1Gb)。也可以指定其他参数 |
HEAP_PROFILE_INUSE_INTERVAL | 每增加N Byte dump一次。默认值104857600 (100 Mb) |
HEAP_PROFILE_TIME_INTERVAL | 时间间隔,每隔N Seconds dump一次 |
HEAP_PROFILE_BUFFER_SIZE | 设置dump出来的heap文件的最大size。(gperftools源代码中无此参数,代码中的buffer默认为1M,经测试1M的情况下会丢弃不少数据,故建议设置100M(104857600B) |
HEAPPROFILESIGNAL | 可设置dump一次heap文件的信号量(尽量挑选没有被使用到的信号量,比如SIGUSR1(10)和SIGUSR2(12))。 |
- 分析堆数据
使用pprof查看数据分析结果:- 文本方式查看, 可以明确的看出,内存泄漏在func2上
[root@yms:/mnt/data/yms/study]pprof --text build/gperftools_demo/mem_leak /tmp/mem_leak.0001.heap Using local file build/gperftools_demo/mem_leak. Using local file /tmp/mem_leak.0001.heap. Total: 100.0 MB 99.0 99.0% 99.0% 99.0 99.0% func2 1.0 1.0% 100.0% 1.0 1.0% func1 0.0 0.0% 100.0% 0.0 0.0% allocate_dtv 0.0 0.0% 100.0% 0.0 0.0% std::thread::_S_make_state 0.0 0.0% 100.0% 100.0 100.0% __GI___clone 0.0 0.0% 100.0% 0.0 0.0% __libc_start_main 0.0 0.0% 100.0% 0.0 0.0% __pthread_create_2_1 0.0 0.0% 100.0% 0.0 0.0% _start 0.0 0.0% 100.0% 0.0 0.0% allocate_stack (inline) 0.0 0.0% 100.0% 0.0 0.0% main 0.0 0.0% 100.0% 1.0 1.0% main::{lambda#1}::operator 0.0 0.0% 100.0% 99.0 99.0% main::{lambda#2}::operator 0.0 0.0% 100.0% 100.0 100.0% start_thread 0.0 0.0% 100.0% 1.0 1.0% std::__invoke@27bf 0.0 0.0% 100.0% 99.0 99.0% std::__invoke@28e5 0.0 0.0% 100.0% 100.0 100.0% std::__invoke_impl 0.0 0.0% 100.0% 100.0 100.0% std::error_code::default_error_condition 0.0 0.0% 100.0% 100.0 100.0% std::thread::_Invoker::_M_invoke 0.0 0.0% 100.0% 100.0 100.0% std::thread::_Invoker::operator 0.0 0.0% 100.0% 0.0 0.0% std::thread::_M_start_thread 0.0 0.0% 100.0% 100.0 100.0% std::thread::_State_impl::_M_run 0.0 0.0% 100.0% 0.0 0.0% std::thread::thread
- 转换成pdf
首先要安装graphviz
然后转换成pdfsudo apt install graphviz
打开pdf[root@yms:/mnt/data/yms/study]pprof --pdf build/gperftools_demo/mem_leak /tmp/mem_leak.0001.heap > mem_leak.pdf Using local file build/gperftools_demo/mem_leak. Using local file /tmp/mem_leak.0001.heap. Dropping nodes with <= 0.5 MB; edges with <= 0.1 abs(MB
- 转换成网页
pprof --web ./my_program /tmp/my_heap_profile.0001.heap
思考
好的工具,可以事半功倍,要持续学习使用新工具,人类和动物最大的区别就是学会使用了工具