文章目录

  • 使用背景
  • TCMalloc 简介
  • TCMalloc 与 gperftools
  • gperftools使用
    • gperftools 安装
      • 编译安装libunwind
      • 编译安装perftools
      • 将TCMalloc库载入到Linux系统中:
    • TCMalloc
      • 使用方法
        • 方式1:编译时链入 -ltcmalloc 库即可
        • 方式2: 使用 LD_PRELOAD
        • tcmalloc加载原理
      • 内存分配效率
        • 官方数据
        • 自测演示
      • 性能调优
      • 注意事项
    • **Heap-Checker**
      • 使用方法
      • 测试demo
      • 检测结果演示
      • 可视化分析
      • Heap Checker 调优
    • **Heap-Profiler**
      • 使用方法
      • 测试demo
      • 分析结果演示
      • Heap-Profiler调优
    • **CPU-Profiler**
  • TCMalloc原理简介
  • 典型应用
  • 工作环境中使用

使用背景

​ 先简单介绍下背景,工作中有一个报警组件,为了不暴露敏感信息,且叫它Alarm吧,它主要负责处理报警相关的业务。随着报警处理量的增加,线上环境出现服务内存一直增长的问题,一开始怀疑是内存泄漏导致的,但是经过一系列排查分析后,虽然解决了一些内存管理不当的问题,但服务的内存仍然会逐渐增长,从增长速度上来看,前期增长速度较快,一天100M左右,到后面增长速度较慢几天或一周100M的样子。因此怀疑是glibc库,在多线程情况下,自身的内存分配机制产生的内存碎片导致服务内存一直增长(内存碎片产生原因)。为了解决这个问题,计划引入tcmalloc库来替换glibc, 从而优化服务的内存管理。

TCMalloc 简介

​ TCMalloc 是 Google 对 C 的 malloc() 和 C++ 的 new 运算符的定制实现,用于我们的 C 和 C++ 代码中的内存分配。这种自定义内存分配框架是 C 标准库(在 Linux 通常通过 glibc)和 C++ 标准库提供的框架的替代方案。通常情况下,TCMalloc在多线程高并发,复杂内存管理的程序中效率更高。

​ TCMalloc 全称为 Thread Caching Malloc ,即线程缓存的malloc 。这是早期TCMalloc的主要特性,但实际上,TCMalloc支持两种运行方式,per-CPU caching(默认)和 per-thread caching。这两种模式都采用缓存的方式,避免内存分配和释放过程中的锁竞争,从而提高内存管理的效率。

​ per-CPU caching(默认):TCMalloc 为单个逻辑核心(CPU)维护本地内存缓存。在任何使用可重启序列 (RSEQ) 的 Linux 内核上运行 TCMalloc 时,会启用per-CPU 缓存。在 Linux 4.18 中合并了对 RSEQ 的支持。

​ per-thread caching: TCMalloc 为每个应用线程维护内存缓存。如果 RSEQ 不可用,TCMalloc 将恢复使用该模式。

TCMalloc 与 gperftools

​ 现在可用的TCMalloc开源项目有两个,tcmalloc 和 gperftools, 其中tcmalloc 要求C++ 代码支持C++17(开发生产环境不支持),C代码支持 C11,gperftool不仅支持tcmalloc还集成了其他分析工具: heap-checker, heap-profiler, cpu-profiler,pprof 。

gperftools使用

gperftools 安装

​ 下载 gperftools-2.10.tar.gz 和 libunwind-1.6.0.tar.gz

编译安装libunwind

​ 64位操作系统请先安装 libunwind库,32位操作系统不要安装。libunwind库为基于64位CPU和操作系统的程序提供了主要的堆栈辗转开解功能。当中包含用于输出堆栈跟踪的API、用于以编程方式辗转开解堆栈的API以及支持C++异常处理机制的API。

# 也可以指定安装路径,命令待补充
1. tar zxvf libunwind-1.6.0.tar.gz
2. cd libunwind-1.6.0# 如果要安装到指定路径,可使用--prefix参数,如: ./configure --prefix=/opt/temp/libunwind-1.1 ,建议使用默认安装
3. ./configure
4. make
5. make install

编译安装perftools

# 也可以指定安装路径,命令待补充
1. tar zxvf tar zxvf gperftools-2.10.tar.gz
2. cd gperftools-2.10# 如果libunwind安装时指定了路径,则需要按如下方式进行相应配置
# ./configure CXXFLAGS="-I/opt/temp/libunwind-1.1/include -fpermissive -g"
# LDFLAGS="-L/opt/temp/libunwind-1.1/lib"
# LIBS="-lunwind"
# --prefix=/opt/temp/google-perftools-2.0
3. ./configure
4. make
5. make install# 还可以通过配置禁用部分工具,如:
# ./configure --disable-cpu-profiler --disable-heap-profiler --disable-heap-checker --disable-debugalloc --enable-minimal --disable-shared

将TCMalloc库载入到Linux系统中:

# 将google-perftools,libunwind库的路径加入系统搜索路径,如果前面指定了安装路径,下面的路径需要修改
echo "/usr/local/lib" > /etc/ld.so.conf.d/usr_local_lib.conf# ldconfig命令的作用主要是在默认搜寻目录/lib和/usr/lib以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索出可共享的动态链接库,进而创建出动态装入程序(ld.so)所需的连接和缓存文件。
/sbin/ldconfig

TCMalloc

使用方法

方式1:编译时链入 -ltcmalloc 库即可

g++/gcc [source_files] -o -g [program] -ltcmalloc
./[program]

方式2: 使用 LD_PRELOAD

​ 可以通过使用LD_PRELOAD在不是你自己编译的应用中使用tcmalloc. 环境变量LD_PRELOAD指定程序运行时优先加载的动态连接库,这个动态链接库中的符号优先级是最高的。标准C的各种函数都是存放在libc.so.6的文件中,在程序运行时自动链接。使用LD_PRELOAD后,自己编写的malloc的加载顺序高于glibc中的malloc,这样就实现了替换.

LD_PRELOAD=/usr/local/lib/libtcmalloc.so ./[your_program]
# 或者
export LD_PRELOAD=/usr/local/lib/libtcmalloc.so
./[your_program]

​ 注意TCMalloc还包含了heap-checker, heap-profiler,如果你更想链接不包含堆分析器和检查器的TCMalloc版本(比如可能为了减少静态二进制文件的大小),你应该链接libtcmalloc_minimal.so

➜  memory_test01 git:(master) LD_PRELOAD=/usr/local/lib/libtcmalloc_minimal.so ./memory_test_09
Used Time:0.014101
➜  memory_test01 git:(master) ./memory_test_09
Used Time:0.028526

tcmalloc加载原理

​ 为什么指定-ltcmalloc或者与libtcmalloc.so连接之后,对malloc、free、new、delete等的调用就由默认的libc中的函数调用变为TCMalloc中相应的函数调用了呢?

​ 在glibc中,内存分配相关的函数都是弱符号(weak symbol),因此TCMalloc只需要定义自己的函数将其覆盖即可,答案在:libc_override_redefine.h中, 以malloc和free为例:

extern "C" { void* malloc(size_t s)                         { return tc_malloc(s);       }void  free(void* p)                            { tc_free(p);                }
}  // extern "C"

​ 可以看到,TCMalloc将malloc()free()分别定义为对tc_malloc()tc_free()的调用,并在tc_malloc()tc_free()中实现具体的内存分配和回收逻辑。

new和delete也类似:

void* operator new(size_t size)                  { return tc_new(size);       }
void operator delete(void* p) CPP_NOTHROW        { tc_delete(p);              }

内存分配效率

官方数据

​ 官方测试环境:2.4GHz 双核心Xeon的RedHat 9系统上,并启用了超线程技术, 使用了Linux glibc-2.3.2,每个测试中进行一百万次操作。在每个案例中,一次正常运行,一次使用LD_PRELOAD=libtcmalloc.so

上图中反映了指定线程数情况下,内存分配效率与分配内存大小之间的关系:

1.多线程情况下(2-20线程),对于较小的内存分配,tcmalloc可以达到7-9million ops/sec (百万次操作/秒),随着分配内存的变大,逐渐降低到2million ops/sec 左右,相比之下,PTMalloc2的性能要差的多——对于小的分配,峰值约为4mops/sec,而对于大的分配,则下降到< 1million ops/sec 。

2.在绝大多数情况下,TCMalloc 比 PTMalloc2 快,特别是对于小分配。线程之间的争用在 TCMalloc 中不是问题。

3.各种线程情况下,内存分配的效率均随着分配大小的变大而降低,原因在与,当线程缓存达到阈值(默认2M)后会触发垃圾回收机制,在阈值一定的情况下,单次分配的内存越大,触发垃圾回收前,缓存中可以分配的对象就越少。

4.TCMalloc的性能在最大分配大小约32K时明显下降;在较大的尺寸下,性能下降得不那么快。这是因为每个线程缓存中的对象的最大大小为32K;对于大于此的对象,TCMalloc从中央堆中分配。

​ 上图中反映了指定内存分配大小情况下,内存分配效率与线程数之间的关系。在这里我们再次看到 TCMalloc 比 PTMalloc2 更加一致和高效。对于 <32K 的最大分配大小,TCMalloc 通常在大量线程的情况下实现,每秒(CPU 时间)约 2-2.5百万次的 操作,而 PTMalloc 通常在每秒(CPU 时间)实现 0.5-1 百万次操作。超过 32K 最大分配大小,TCMalloc 下降到 CPU 时间每秒 1-1.5 百万次操作,而 PTMalloc 对于大量线程几乎下降到零(即使用 PTMalloc,大量 CPU 时间被消耗在大量等待锁定多线程案例)。

自测演示

time_elapse.hpp

#include <chrono>
struct time_elapse
{void start(){start_ = std::chrono::steady_clock::now();}double end(){std::chrono::duration<double> dura = std::chrono::steady_clock::now() - start_;return dura.count();}private:std::chrono::time_point<std::chrono::steady_clock> start_ = std::chrono::steady_clock::now();
};

memory_test_10.cpp

#include <thread>
#include <iostream>
#include <memory>
#include <vector>
#include <functional>
#include "time_elapse.hpp"
using namespace std;template <int N>
class TestObjects
{public:
private:enum{size = N * 1024};char m_content[size];
};#define SIZE 100000
#define LOOP 5template <int N>
void mallocobjectfunc()
{typedef TestObjects<N> TObject;int n = 0;double total = 0;time_elapse te;for (; n < LOOP; n++){te.start();{vector<shared_ptr<TObject>> m_vobjc;m_vobjc.reserve(SIZE);int i = 0;for (; i < SIZE; i++){shared_ptr<TObject> p(new TObject);m_vobjc.push_back(p);}}total += te.end();}cout << "malloc " << N << " kb avg use " << total / LOOP << "s"<< ", operation:" << SIZE << ", loop:" << LOOP << endl;
}int main()
{//单线程测试//测试小于32K 小对象{mallocobjectfunc<14>();}//测试大于32K 小对象{mallocobjectfunc<40>();}//多线程//测试小于32K 小对象cout << "=================multi thread 3kb================" << endl;{thread t1(bind(&mallocobjectfunc<3>));thread t2(bind(&mallocobjectfunc<3>));thread t3(bind(&mallocobjectfunc<3>));thread t4(bind(&mallocobjectfunc<3>));thread t5(bind(&mallocobjectfunc<3>));t1.join();t2.join();t3.join();t4.join();t5.join();}//测试大于32K 小对象cout << "=================multi thread 35kb================" << endl;{thread t1(bind(&mallocobjectfunc<35>));thread t2(bind(&mallocobjectfunc<35>));thread t3(bind(&mallocobjectfunc<35>));thread t4(bind(&mallocobjectfunc<35>));thread t5(bind(&mallocobjectfunc<35>));t1.join();t2.join();t3.join();t4.join();t5.join();}return 0;
}

测试结果如下,从中可以看出,无论单线程还是多线程,对于小内存分配,tcmalloc分配释放内存的效率明显要比ptmalloc高很多,对于大内存tcmalloc则略微弱于ptmalloc(这点可能是因为平台差异,与官方数据略微有点差异)(这种测试代码可能不太科学, 因为这种申请后马上释放,再申请,可能会复用前一次的内存(释放给系统后,不会马上归还到虚拟/物理内存) 公平的测试方法是随机分配(随机大小)、随机释放 。tcmalloc在多线程,小内存下才显优势)


➜  memory_test01 git:(master) ✗ g++ memory_test_10.cpp time_elapse.hpp -o memory_test_10 -g -lpthread
➜  memory_test01 git:(master) ✗ g++ memory_test_10.cpp time_elapse.hpp -o memory_test_10_tc -g -ltcmalloc -lpthread➜  memory_test01 git:(master) ✗ ./memory_test_10
malloc 14 kb avg use 0.116988s, operation:100000, loop:5
malloc 40 kb avg use 0.124155s, operation:100000, loop:5
=================multi thread 3kb================
malloc 3 kb avg use 0.127944s, operation:100000, loop:5
malloc 3 kb avg use 0.129009s, operation:100000, loop:5
malloc 3 kb avg use 0.129067s, operation:100000, loop:5
malloc 3 kb avg use 0.129132s, operation:100000, loop:5
malloc 3 kb avg use 0.129109s, operation:100000, loop:5
=================multi thread 35kb================
malloc 35 kb avg use 0.178649s, operation:100000, loop:5
malloc 35 kb avg use 0.179203s, operation:100000, loop:5
malloc 35 kb avg use 0.181363s, operation:100000, loop:5
malloc 35 kb avg use 0.181391s, operation:100000, loop:5
malloc 35 kb avg use 0.184618s, operation:100000, loop:5➜  memory_test01 git:(master) ✗ ./memory_test_10_tc
malloc 14 kb avg use 0.0678092s, operation:100000, loop:5
malloc 40 kb avg use 0.0638737s, operation:100000, loop:5
=================multi thread 3kb================
malloc 3 kb avg use 0.0801741s, operation:100000, loop:5
malloc 3 kb avg use 0.0809957s, operation:100000, loop:5
malloc 3 kb avg use 0.081022s, operation:100000, loop:5
malloc 3 kb avg use 0.0813797s, operation:100000, loop:5
malloc 3 kb avg use 0.0815251s, operation:100000, loop:5
=================multi thread 35kb================
malloc 35 kb avg use 0.264688s, operation:100000, loop:5
malloc 35 kb avg use 0.266015s, operation:100000, loop:5
malloc 35 kb avg use 0.281328s, operation:100000, loop:5
malloc 35 kb avg use 0.287777s, operation:100000, loop:5
malloc 35 kb avg use 0.28829s, operation:100000, loop:5

性能调优

可以通过环境变量来控制tcmalloc的行为,通常有用的标志。

Generally useful flags:

TCMALLOC_SAMPLE_PARAMETER default: 0 The approximate gap between sampling actions. That is, we take one sample approximately once every tcmalloc_sample_parmeter bytes of allocation. This sampled heap information is available via MallocExtension::GetHeapSample() or MallocExtension::ReadStackTraces(). A reasonable value is 524288. 采样时间间隔
TCMALLOC_RELEASE_RATE default: 1.0 Rate at which we release unused memory to the system, via madvise(MADV_DONTNEED), on systems that support it. Zero means we never release memory back to the system. Increase this flag to return memory faster; decrease it to return memory slower. Reasonable rates are in the range [0,10]. 释放未使用内存的概率,
TCMALLOC_LARGE_ALLOC_REPORT_THRESHOLD default: 1073741824 Allocations larger than this value cause a stack trace to be dumped to stderr. The threshold for dumping stack traces is increased by a factor of 1.125 every time we print a message so that the threshold automatically goes up by a factor of ~1000 every 60 messages. This bounds the amount of extra logging generated by this flag. Default value of this flag is very large and therefore you should see no extra logging unless the flag is overridden. 内存最大分配阈值
TCMALLOC_MAX_TOTAL_THREAD_CACHE_BYTES default: 16777216 Bound on the total amount of bytes allocated to thread caches. This bound is not strict, so it is possible for the cache to go over this bound in certain circumstances. This value defaults to 16MB. For applications with many threads, this may not be a large enough cache, which can affect performance. If you suspect your application is not scaling to many threads due to lock contention in TCMalloc, you can try increasing this value. This may improve performance, at a cost of extra memory use by TCMalloc. See Garbage Collection for more details. 分配给线程缓冲的最大内存上限

高级参数配置,可以更精确地控制tcmalloc如何尝试从内核分配内存

TCMALLOC_SKIP_MMAP default: false If true, do not try to use mmap to obtain memory from the kernel.
TCMALLOC_SKIP_SBRK default: false If true, do not try to use sbrk to obtain memory from the kernel.
TCMALLOC_DEVMEM_START default: 0 Physical memory starting location in MB for /dev/mem allocation. Setting this to 0 disables /dev/mem allocation.
TCMALLOC_DEVMEM_LIMIT default: 0 Physical memory limit location in MB for /dev/mem allocation. Setting this to 0 means no limit.
TCMALLOC_DEVMEM_DEVICE default: /dev/mem Device to use for allocating unmanaged memory.
TCMALLOC_MEMFS_MALLOC_PATH default: “” If set, specify a path where hugetlbfs or tmpfs is mounted. This may allow for speedier allocations.
TCMALLOC_MEMFS_LIMIT_MB default: 0 Limit total memfs allocation size to specified number of MB. 0 means “no limit”.
TCMALLOC_MEMFS_ABORT_ON_FAIL default: false If true, abort() whenever memfs_malloc fails to satisfy an allocation.
TCMALLOC_MEMFS_IGNORE_MMAP_FAIL default: false If true, ignore failures from mmap.
TCMALLOC_MEMFS_MAP_PRIVATE default: false If true, use MAP_PRIVATE when mapping via memfs, not MAP_SHARED.

注意事项

  1. TCMalloc可能要比其他malloc版本在某种程度上更吃内存,(但是倾向于不会有其他malloc版本中可能出现的爆发性增长。)尤其是在启动时TCMalloc会分配大约240KB的内部内存。
  2. 不要试图将TCMalloc载入到一个运行中的二进制程序中(例如,在Java中使用JNI)。二进制程序已经使用系统malloc分配了一些对象,并会尝试将它们传递到TCMalloc进行解除分配。TCMalloc是无法处理这种对象的。

Heap-Checker

​ 内存泄漏的检测方法有两类,侵入式和非侵入式的,侵入式的方式需要将代码手动写入程序中,可以精准控制检查指定代码段的内存泄漏情况或者忽略某段已知的内存泄漏代码。具体的说明,这里不作赘述,详见官方文档。非侵入式的,只要在编译或运行时将tcmalloc的库连接到程序中即可。

​ 内存泄漏的检测方式有4种,按检测的严格程度依次为:minimalnormalstrictdraconiannormal模式检查跟踪活动对象并报告程序退出时无法通过活动对象访问的任何数据的泄漏,它适用于日常堆检查使用。通常情况下使用normal模式即可,这也是google内部最常用的方式。其他详细介绍 官方说明文档

使用方法

# 1. 连接tcmalloc , 带上参数 -g 可以方便的定位哪一行发生了泄漏
g++ memory_test_01.cpp -o -g memory_test_tc -ltcmalloc -std=c++11
# 2.设置环境变量
export HEAPCHECK=normal
# 3.执行程序
./memory_test_tc # 或者2,3合并
env HEAPCHECK=normal ./memory_test_tc
# 编译时如果没有使用 -ltcmalloc ,也可以使用以下方式进行检测
LD_PRELOAD="/usr/local/lib/libtcmalloc.so" env HEAPCHECK=normal  /memory_test_tc

测试demo

#include <iostream>
#include <thread>
#include <chrono>
#include <malloc.h>
using namespace std;const int32_t k1024KB = 1024 * 1024;
const int32_t k1KB = 1 * 1024;void print_mem_info(const char *s)
{printf("------------------------\n");printf("-- %s\n", s);printf("------------------------\n");malloc_stats();printf("========================\n\n");
}
int main()
{cout << "=====memmory leak test=====" << endl;// 测试发现最小2kb能检测出来,1kb就检测不出结果,但是也没找到要配置char *p = new char[2 * k1KB];// 发现这里不打印下,内存泄露也检测不出来,运行太快了?试了下不加这一行,sleep一下,并不行。可能代码太简单了?print_mem_info("111");printf("=============end============\n");return 0;
}

检测结果演示

➜  memory_test01 git:(master) ✗ g++ memory_test_01.cpp -o memory_test_tc -ltcmalloc -std=c++11
➜  memory_test01 git:(master) ✗ ./memory_test_tc
# A
WARNING: Perftools heap leak checker is active -- Performance may suffer
Have memory regions w/o callers: might report false leaks
Have memory regions w/o callers: might report false leaks
Found leaks without pointer alignment as well: unaligned pointers must not be the cause of leaks.
--heap_check_test_pointer_alignment did not help to diagnose the leaks.
Leak check _main_ detected leaks of 2048 bytes in 1 objects
The 1 largest leaks:
Using local file ./memory_test_tc.
addr2line: DWARF error: section .debug_info is larger than its filesize! (0x93ef57 vs 0x530ea0)
Leak of 2048 bytes in 1 objects allocated from:@ 557264f952bb main@ 7f5d03976083 __libc_start_main@ 557264f9516e _startIf the preceding stack traces are not enough to find the leaks, try running THIS shell command:pprof ./memory_test_tc "/tmp/memory_test_tc.75522._main_-end.heap" --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --gvIf you are still puzzled about why the leaks are there, try rerunning this program with HEAP_CHECK_TEST_POINTER_ALIGNMENT=1 and/or with HEAP_CHECK_MAX_POINTER_OFFSET=-1
If the leak report occurs in a small fraction of runs, try running with TCMALLOC_MAX_FREE_QUEUE_SIZE of few hundred MB or with TCMALLOC_RECLAIM_MEMORY=false, it might help find l
Exiting with error code (instead of crashing) because of whole-program memory leaks

​ 可以看出,如果有内存泄漏,A 处会打印出详细的内存泄漏报告,和一些分析提示。如可以根据提示,对检测结果进行可视化分析:

可视化分析

​ 通过 --text --stacks 可以打印出堆栈信息,也可以用 --web或–gv打印出可视化图像,从中可以方便的看出内存泄漏发生在22行处。 这里注意,编译时要上参数-g, 否则,无法准确定位内存泄漏的位置。

# 泄漏分析-通过pprof工具进行可视化分析
pprof ./memory_test_tc "/tmp/memory_test_tc.75522._main_-end.heap" --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --gv
# 如果提示无法执行gv, sudo apt-get install graphviz gv 即可
# 报告的模式可以是text,web,stacks等,详细使用方法可以使用pprof --help查阅, 其中web方式为泄漏关系图,比较直观

Heap Checker 调优

Heap leak checker有很多可选配置,可通过环境变量的方式进行配置。以下各个参数基本都保持默认即可。详细见官方文档

第一组选项控制灵敏度和准确性。在 as-is (详细解释见官方文档)模式下运行堆检查器时这些选项才有效.

HEAP_CHECK_AFTER_DESTRUCTORS Default: false When true, do the final leak check after all other global destructors have run. When false, do it after all REGISTER_HEAPCHECK_CLEANUP, typically much earlier in the global-destructor process.
HEAP_CHECK_IGNORE_THREAD_LIVE Default: true If true, ignore objects reachable from thread stacks and registers (that is, do not report them as leaks).
HEAP_CHECK_IGNORE_GLOBAL_LIVE Default: true If true, ignore objects reachable from global variables and data (that is, do not report them as leaks).

这些选项修改了整个程序泄漏检查的行为。

HEAP_CHECK_MAX_LEAKS Default: 20 The maximum number of leaks to be printed to stderr (all leaks are still emitted to file output for pprof to visualize). If negative or zero, print all the leaks found

这些选项适用于所有类型的泄漏检查。

HEAP_CHECK_IDENTIFY_LEAKS Default: false If true, generate the addresses of the leaked objects in the generated memory leak profile files.(如果为 true,则在生成的内存泄漏配置文件中生成泄漏对象的地址)
HEAP_CHECK_TEST_POINTER_ALIGNMENT Default: false If true, check all leaks to see if they might be due to the use of unaligned pointers.(如果为真,检查所有泄漏,看看它们是否可能是由于使用了未对齐的指针造成的)
HEAP_CHECK_POINTER_SOURCE_ALIGNMENT Default: sizeof(void*) Alignment at which all pointers in memory are supposed to be located. Use 1 if any alignment is ok.(内存中所有指针应该位于的对齐方式。如果任何对齐都可以,请使用 1)
PPROF_PATH Default: pprof The location of the pprof executable.(pprof可执行文件的位置)
HEAP_CHECK_DUMP_DIRECTORY Default: /tmp Where the heap-profile files are kept while the program is running.(内存检测文件的生成位置)

Heap-Profiler

gperftool中还提供了堆内存分析工具,通过堆内存分析,可以分析任意给定时刻内存中的数据,分析代码中的内存消耗瓶颈,也可以用来定位内存泄漏。使用方法和上文中的Heap-Checker类似。

使用方法

# 1.编译代码
g++ memory_test_04.cpp -g -o memory_test_tc4 -ltcmalloc -std=c++11
# 2.指定分析文件生成路径,并开启堆内存跟踪
env HEAPPROFILE=/tmp/test.hprof ./memory_test_tc4

测试demo

#include <iostream>// 手动指定分析片段时使用
// #include "gperftools/heap_profiler.h"const int32_t k1024KB = 1024 * 1024;// 分配100MB内存
void test1()
{int i = 100;while (i--){char *buf = new char[k1024KB];}
}// 分配100MB内存
void test2()
{int i = 100;while (i--){char *buf = new char[k1024KB];}
}int main()
{// 手动指定分析片段// HeapProfilerStart("./test.log");int i = 100;while (i--){char *buf = new char[k1024KB];}test1();test2();// HeapProfilerStop();return 0;
}

分析结果演示

可通过pprof工具进行转存文件分析,常用的有 --focus --ignore等

--inuse_space Display the number of in-use megabytes (i.e. space that has been allocated but not freed). This is the default.(分配未释放的内存)
--inuse_objects Display the number of in-use objects (i.e. number of objects that have been allocated but not freed).(分配未时释放的对象)
--alloc_space Display the number of allocated megabytes. This includes the space that has since been de-allocated. Use this if you want to find the main allocation sites in the program.(分配的内存总数)
--alloc_objects Display the number of allocated objects. This includes the objects that have since been de-allocated. Use this if you want to find the main allocation sites in the program.(分配的对象总数)
➜  memory_test01 git:(master) env HEAPPROFILE=/tmp/test.hprof ./memory_test_tc4
Starting tracking the heap
Dumping heap profile to /tmp/test.hprof.0001.heap (101 MB currently in use)
Dumping heap profile to /tmp/test.hprof.0002.heap (202 MB currently in use)
Dumping heap profile to /tmp/test.hprof.0003.heap (Exiting, 300 MB in use)

​ 可以单独分析其中的一个快照:

# 命令,可以通过--focus=test1, --ignore=test1 指定或忽略某些显示项
# pprof --text --ignore=test1 ./memory_test_tc4 /tmp/test.hprof.0003.heap
# pprof --text --focus=test1 ./memory_test_tc4 /tmp/test.hprof.0003.heap
➜pprof --text ./memory_test_tc4 /tmp/test.hprof.0003.heap
# 结果
Using local file ./memory_test_tc4.
Using local file /tmp/test.hprof.0003.heap.
addr2line: DWARF error: section .debug_info is larger than its filesize! (0x93ef57 vs 0x530ea0)
Total: 300.0 MB100.0  33.3%  33.3%    300.0 100.0% main100.0  33.3%  66.7%    100.0  33.3% test1100.0  33.3% 100.0%    100.0  33.3% test20.0   0.0% 100.0%    300.0 100.0% __libc_start_main0.0   0.0% 100.0%    300.0 100.0% _start# The first column contains the direct memory use in MB.
# The fourth column contains memory use by the procedure and all of its callees.
# The second and fifth columns are just percentage representations of the numbers in the first and fourth columns.
# The third column is a cumulative sum of the second column (i.e., the kth entry in the third column is the sum of the first k entries in the second column.)

​ 从上面的结果中可以看出,main函数中一共消耗了300M内存,test1,test2各占了约33%。

​ 线上运行环境中,有时需要对比某一段时间内的内存快照来分析其中逐渐产生的内存泄漏,这种情况下就需要对两个快照进行比较,发现其中可能出现的内存泄漏情况。

pprof --base=/tmp/test.hprof.0001.heap ./memory_test_tc4 /tmp/test.hprof.0003.heap

​ 运行上述命令后会进入交互窗口,可以使用help查找命令说明,根据提示对内存进行分析,例如,0003.heap比0001.heap大了199M,其中test1和test2分别占了约50%

(pprof) top
Total: 199.0 MB100.0  50.3%  50.3%    100.0  50.3% test299.0  49.7% 100.0%     99.0  49.7% test10.0   0.0% 100.0%    199.0 100.0% __libc_start_main0.0   0.0% 100.0%    199.0 100.0% _start0.0   0.0% 100.0%    199.0 100.0% main

​ 如果同时开启了内存泄漏检测: export HEAPCHECK=normal,则可见如下结果:

➜  memory_test01 git:(master) env HEAPPROFILE=/tmp/test.hprof ./memory_test_tc4
WARNING: Perftools heap leak checker is active -- Performance may suffer
Starting tracking the heap
Dumping heap profile to /tmp/test.hprof.0001.heap (101 MB currently in use)
Dumping heap profile to /tmp/test.hprof.0002.heap (202 MB currently in use)
Dumping heap profile to /tmp/test.hprof.0003.heap (Exiting, 300 MB in use)
Have memory regions w/o callers: might report false leaks
Leak check _main_ detected leaks of 313524224 bytes in 299 objects
The 3 largest leaks:
*** WARNING: Cannot convert addresses to symbols in output below.
*** Reason: Cannot find 'pprof' (is PPROF_PATH set correctly?)
*** If you cannot fix this, try running pprof directly.
Leak of 104857600 bytes in 100 objects allocated from:@ 55a96ec2d1b8 @ 55a96ec2d233 @ 7f3c3e431083 @ 55a96ec2d0ce
Leak of 104857600 bytes in 100 objects allocated from:@ 55a96ec2d228 @ 7f3c3e431083 @ 55a96ec2d0ce
Leak of 103809024 bytes in 99 objects allocated from:@ 55a96ec2d1f0 @ 55a96ec2d238 @ 7f3c3e431083 @ 55a96ec2d0ce If the preceding stack traces are not enough to find the leaks, try running THIS shell command:pprof ./memory_test_tc4 "/tmp/memory_test_tc4.442360._main_-end.heap" --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --gvIf you are still puzzled about why the leaks are there, try rerunning this program with HEAP_CHECK_TEST_POINTER_ALIGNMENT=1 and/or with HEAP_CHECK_MAX_POINTER_OFFSET=-1
If the leak report occurs in a small fraction of runs, try running with TCMALLOC_MAX_FREE_QUEUE_SIZE of few hundred MB or with TCMALLOC_RECLAIM_MEMORY=false, it might help fin
Exiting with error code (instead of crashing) because of whole-program memory leaks

按提示可视化分析

pprof ./memory_test_tc4 "/tmp/memory_test_tc4.110961._main_-end.heap" --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --web

使用–web参数可以显示程序的调用关系图,底部的方框中详细展示出了内存泄漏的位置泄漏所占的比例

Heap-Profiler调优

同样可以通过环境变量的方式来对内存分析进行配置,参数如下:

HEAP_PROFILE_ALLOCATION_INTERVAL default: 1073741824 (1 Gb) Dump heap profiling information each time the specified number of bytes has been allocated by the program.(分配指定的内存数时转存堆分析信息,默认1G)
HEAP_PROFILE_INUSE_INTERVAL default: 104857600 (100 Mb) Dump heap profiling information whenever the high-water memory usage mark increases by the specified number of bytes.(增加指定的内存数时转存堆分析信息,默认100M)
HEAP_PROFILE_TIME_INTERVAL default: 0 Dump heap profiling information each time the specified number of seconds has elapsed.(每次经过指定的秒数就转储堆分析信息)
HEAPPROFILESIGNAL default: disabled Dump heap profiling information whenever the specified signal is sent to the process.(每当向进程发送指定的信号时,转储堆分析信息。)
HEAP_PROFILE_MMAP default: false (除了 malloc、calloc、realloc 和 new 之外,还分析 mmap、mremap 和 sbrk 调用。)Profile mmap, mremap and sbrk calls in addition to malloc, calloc, realloc, and new. NOTE: this causes the profiler to profile calls internal to tcmalloc, since tcmalloc and friends use mmap and sbrk internally for allocations. One partial solution is to filter these allocations out when running pprof, with something like `pprof --ignore='DoAllocWithArena
HEAP_PROFILE_ONLY_MMAP default: false Only profile mmap, mremap, and sbrk calls; do not profile malloc, calloc, realloc, or new.(仅分析 mmap、mremap 和 sbrk 调用;不要分析 malloc、calloc、realloc 或 new。)
HEAP_PROFILE_MMAP_LOG default: false Log mmap/munmap calls.(记录 mmap/munmap 调用)
HEAPPROFILE 指定分析文件的生成路径:HEAPPROFILE=/tmp/mybin.hprof

CPU-Profiler

cpu-profiler可用来进行cpu分析,基本使用方法如下

# 1. 连接lprofiler, 带上参数 -g 可以方便的定位哪一行发生了泄漏
g++ memory_test_01.cpp -o -g memory_test_tc -lprofiler -std=c++11
# 2.开启分析
env CPUPROFILE=memory_test_tc.prof  ./memory_test_tc
# 编译时如果没有使用 -lprofiler ,也可以使用以下方式进行检测
LD_PRELOAD="/usr/lib/libprofiler.so" env CPUPROFILE=memory_test_tc.prof  /memory_test_tc

因为在x86-64位系统上,cpu分析器工具是不可靠的(tcmalloc本身工作正常):它有时会工作,但有时会导致段错误,而且使用方法与堆分析器类似,所以这里不再详细介绍了,具体可查看官方文档。

TCMalloc原理简介

​ 相较与ptmalloc, tcmalloc的主要优势是:多线程情况下,小内存的分配效率非常高。其原因主要在于:使用了thread cache(线程cache或CPU cache),小块的内存分配都可以从cache中分配,这样再多线程分配内存的情况下,可以减少锁竞争,这就可以极大提高分配的速度,因为锁/解锁操作在一个2.8GHz Xeon上大约需要100纳秒的时间。同时,当某个线程缓存中所有对象的总大小超过2MB的时候,会进行垃圾收集,回收空闲的内存。垃圾收集阈值会自动根据线程数量的增加而减少,这样就不会因为程序有大量线程而过度浪费内存。

​ tcmalloc的内存分配分为四层。

​ ThreadCache(用于小对象分配):线程本地缓存,每个线程独立维护一个该对象,多线程在并发申请内存时不会产生锁竞争。

​ CentralCache(Central free list,用于小对象分配):全局cache,所有线程共享。当thread cache空闲链表为空时,会批量从CentralCache中申请内存;当thread cache总内存超过阈值,会进行内存垃圾回收,将空闲内存返还给CentralCache。

​ Page Heap(小/大对象):全局页堆,所有线程共享。对于小对象,当centralcache为空时,会从page heap中申请一个span;当一个span完全空闲时,会将该span返还给page heap。对于大对象,直接从page heap中分配,用完直接返还给page heap。

​ 系统内存:当page cache内存用光后,会通过sbrk、mmap等系统调用向OS申请内存。

​ 更多介绍详见:官方文档,TCMalloc解密

典型应用

​ 文中通过引入tcmalloc来提高mysql处理性能,查询性能普遍提高30%。

export LD_PRELOAD="/usr/lib/libtcmalloc_minimal.so"

​ 文中疑似STL 用得过多,加之 ttserver 和 memcached 大量的读操作 —— 大量的小内存分配可能带来全局的锁竞争,从而可能使得 CPU 过高。使用tcmalloc替换malloc后,CPU性能得到了巨大提升。

工作环境中使用

​ 工作环境中如果要解决内存碎片问题,建议使用 libtcmalloc_minimal.so库;如果要使用其中的工具分析内存问题,建议使用 libtcmalloc_and_profiler.so。用法见上文。

可以通过CMake条件编译将库编译到成果物中

# https://cmake.org/cmake/help/latest/policy/CMP0077.html#policy:CMP0077
# When the option() command sees a normal variable of the given name:
# The OLD behavior for this policy is to proceed even when a normal variable of the same name exists. If the cache entry does not already exist and have a type then it is created and/or given a type and the normal variable is removed.
# The NEW behavior for this policy is to do nothing when a normal variable of the same name exists. The normal variable is not removed. The cache entry is not created or updated and is ignored if it exists.# 由于jenkins中使用的cmake版本为3.13.2 需要这个设置,避免WITH_TCMALLOC_INTEGRATE被子目录中的option修改,详见 `cmake --help-policy CMP0077`
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
# 使用TCMalloc
set(WITH_TCMALLOC_INTEGRATE ON)if (WITH_TCMALLOC_INTEGRATE STREQUAL "ON")SET(TC_MALLOC_LIBS${HC_BIN_COMMON_LIB_DIR}/tcmalloc64_linux/libtcmalloc_and_profiler.so.4.6.5${HC_BIN_COMMON_LIB_DIR}/tcmalloc64_linux/libunwind-coredump.so.0.0.0${HC_BIN_COMMON_LIB_DIR}/tcmalloc64_linux/libunwind-ptrace.so.0.0.0${HC_BIN_COMMON_LIB_DIR}/tcmalloc64_linux/libunwind-setjmp.so.0.0.0${HC_BIN_COMMON_LIB_DIR}/tcmalloc64_linux/libunwind.so.8.0.1${HC_BIN_COMMON_LIB_DIR}/tcmalloc64_linux/libunwind-x86_64.so.8.0.1)
endif(WITH_TCMALLOC_INTEGRATE STREQUAL "ON")target_link_libraries(Cyclotronic${TC_MALLOC_LIBS}...
)# 在CMake中设置可在代码生效的宏
if (WITH_TCMALLOC_INTEGRATE STREQUAL "ON")add_compile_definitions(WITH_TCMALLOC_INTEGRATE)
endif(WITH_TCMALLOC_INTEGRATE STREQUAL "ON")# 有了上面的定义WITH_TCMALLOC_INTEGRATE设置为ON时代码中的WITH_TCMALLOC_INTEGRATE宏就被打开
#if defined (WITH_TCMALLOC_INTEGRATE)service_->GET("/application/profiler/memory/stats", &GetMemoryStateHander);...
#endif /* WITH_TCMALLOC_INTEGRATE */

一文搞定google perftool相关推荐

  1. 一文搞定 Google Play 应用的中文设置

    作为开发者们,您或许也遇到过这样的困扰: 在不同地区发布中文版本的应用或游戏时,不确定自己的语言设置是否正确.在这篇文章中,我们将为您分享以下关于中文版本发布的最佳实践,希望助您一臂之力: 不同地区的 ...

  2. php带参数单元测试_一文搞定单元测试核心概念

    基础概念 单元测试(unittesting),是指对软件中的最小可测试单元进行检查和验证,这里的最小可测试单元通常是指函数或者类.单元测试是即所谓的白盒测试,一般由开发人员负责测试,因为开发人员知道被 ...

  3. 【Python基础】一文搞定pandas的数据合并

    作者:来源于读者投稿 出品:Python数据之道 一文搞定pandas的数据合并 在实际处理数据业务需求中,我们经常会遇到这样的需求:将多个表连接起来再进行数据的处理和分析,类似SQL中的连接查询功能 ...

  4. 一文搞定Swing和Qt按钮和文本框的创建

    一文搞定Swing和Qt按钮和文本框的创建 Qt的截图 java的 源码 package com.lujun;import java.awt.Container;import javax.swing. ...

  5. 一文搞定C#关于NPOI类库的使用读写Excel以及io流文件的写出

    一文搞定C#关于NPOI类库的使用读写Excel以及io流文件的写出 今天我们使用NPOI类库读写xlsx文件, 最终实现的效果如图所示 从太平洋官网下载相应的类库,大概4~5MB,不要从github ...

  6. 一文搞定Qt读写excel以及qt读写xml数据

    一文搞定Qt读写excel以及qt读写xml数据 最终的实现效果图 RC_ICONS = logo.ico .pro文件同级目录下加入 logo.ico 图标文件,运行文件,文件的图标就被写入软件 u ...

  7. 一文搞定 Spring Data Redis 详解及实战

    转载自  一文搞定 Spring Data Redis 详解及实战 SDR - Spring Data Redis的简称. Spring Data Redis提供了从Spring应用程序轻松配置和访问 ...

  8. 一文搞定面试中的二叉树问题

    一文搞定面试中的二叉树问题 版权所有,转载请注明出处,谢谢! http://blog.csdn.net/walkinginthewind/article/details/7518888 树是一种比较重 ...

  9. 【全网最全】一文搞定 Linux 压缩、解压哪些事儿

    一文搞定 Linux 压缩.解压哪些事儿 Linux 常用的解压和压缩命令如下: 1..tar # 解包 tar xvf FileName.tar # 打包 tar cvf FileName.tar ...

最新文章

  1. 马斯克的火箭又炸了,这次可能怪美国宇航管理局:因督导员迟到,星舰原型SN11被迫在浓雾中发射...
  2. Ethernet II帧格式
  3. 数据中台产品经理面试指南(二)
  4. java下载zip_从Servlet Java下载zip
  5. C#中使用OpenGL(API)创建OpenGL渲染环境
  6. mysql连表查询最大值_SQL 两个表联合查询记录中取最大值
  7. 需要注意的new Date 时区问题
  8. 《How to debug PS4 game》
  9. php-后台权限的思路
  10. xp计算机启动检测硬盘,WindowsXP系统,每次开机都自动检测硬盘处理办法
  11. 【部署】蓝绿发布、滚动发布、灰度发布,有什么区别
  12. 一款集成微信小助手的mac微信最新版!支持发朋友圈!
  13. 2.1 电子计算机的兴起
  14. 在企业里管理机器学习:来自银行和医疗行业的经验
  15. AVR单片机开发2——流水灯及仿真
  16. 真实案例解析OO理论与实践
  17. 在pycharm中使用pyqt5
  18. The Tomcat server configuration at\...详细步骤
  19. 电脑重启bootmgr_电脑启动不了显示bootmgr
  20. 日期数据数组按实际周和实际月进行分组

热门文章

  1. C++和Java应该怎么选择(转)
  2. 业绩反转,阅文拉开了IP时代的大幕
  3. 基于PanoSim仿真开发平台LDW验证以及LDP的构想
  4. 基于ssm框架的人才招聘网站
  5. 小米路由器mini刷lede_小米路由Mini刷Breed, 潘多拉和LEDE
  6. android多媒体框架介绍(五)显示图形系统之SurfaceFlinger初步介绍
  7. adodb/adodb.inc.php,php adodb连接不同数据库
  8. EXCEL自动填充列索引号EXCEL的列求和、行求和EXCEL第一行锁定WORD行间距规整EXCEL快速筛选WORD画流程图
  9. spark和flink是什么、区别、共同点以及替换性
  10. CCActionManager和PageTurn3D源码解析