内存问题检测工具
C/C++ 语言,程序员可以管理内存,十分便利,但同时灵活的内存访问也带来了很多很多的问题。尤其是那些偶现的,运行成本较高的场景,往往让我们的开发工作效率大大减慢。
一种办法是静态检查,但问题是很多测试场景跑不到,并且误报较多。另外一种就是动态检查工具,本文就是介绍几种 Linux 下运行时的内存检查工具。
一、内存问题
- memory overrun:写内存越界
- double free:同一块内存释放两次
我们的智能驾驶系统,遇到了 Glibc 的内存暴增问题。系统中实现了一个简单的内存管理模块,在高压力环境下长时间运行(加载感知模型),当内存管理模块的内存释放给 C 运行时库之后,C 运行时库并没有立即把内存归还给操作系统。比如占用的内存为 10GB,释放内存后,通过 TOP 命令查看,有时是 10GB、有时是 5GB,有时是 2GB,内存释放行为非常不确定。
我们系统中的内存管理方式比较简单,使用全局的定长内存池,内存管理模块每次分配/释放 2MB 内存,然后分成 64KB 为单位的一个个小内存块用 hash 加链表的方式进行管理。如果申请的内存小于等于 64KB 时,直接从内存池的空闲链表中获取一个内存块,内存释放时归还空闲链表;如果申请的内存大于 64KB,直接通过C运行时库的 malloc 和 free 获取。某些数据结构涉及到很多小对象的管理,这些数据结构从全局内存池获取内存后再根据数据结构的特点进行组织。为了提高内存申请/释放的效率,减少锁冲突,为每一个线程单独保留 8MB 的内存块,每个线程优先从线程专属的 8MB 内存块获取内存,专属内存不足时才从全局的内存池获取。
系统在高压力、高并发环境下长时间运行会发生内存暴增的现象,最终进程被 OOM 掉。
为了便于跟踪分析问题,在全局的内存池中加入对每个子模块的内存统计功能:每个子模块申请内存时都将子模块编号传给全局的内存池,全局的内存池进行统计。复现问题后发现全局的内存池的统计结果符合预期。
然后我们动用 ASAN 、review 代码 确认没有发现内存泄漏。
GC 算法中最重要的两个角色就是 Mutator 和 Collector。
valgrind 工具集包括如下工具
1 | 1、memcheck:检查程序中的内存问题,如泄漏、越界、非法指针等。 |
这几个工具的使用是通过命令:valgrand –tool=name
程序名来分别调用的,当不指定 tool 参数时默认是 –tool=memcheck
1 | 最常用的工具,用来检测程序中出现的内存问题,所有对内存的读写都会被检测到,一切对malloc、free、new、delete的调用都会被捕获。所以,它能检测以下问题: |
KCachegrind 工具,可视化工具
https://kcachegrind.github.io/html/Home.html
KCachegrind是个图形化界面,主要用于对callgrind分析来的数据进行可视化。
https://sourceforge.net/projects/kcachegrind/
安装 valgrind 和 KCachegrind。
1 | 首先安装 valgrind:https://www.valgrind.org/ |
ptmalloc2 在机器 4核 CPU 2.8GHz
上对于小对象,执行 malloc/free
两个操作大约需要 300 ns,而对于同一操作,tcmalloc 只需要大约 50ns。
tcmalloc 减少了多线程程序的锁竞争。对于小对象,使用 TLS,几乎没有竞争。对于大对象,tcmalloc 尝试使用细粒度且高效的自旋锁。在 ptmalloc2 中,不同的分配区之间的内存不能发生移动、复用,可能导致大量的空间浪费。
比如,有一个多阶段的任务,第一阶段为某一个数据结构申请 100MB 内存,第一阶段完成后,释放了空间;在第二阶段,在同一地址空间开始申请内存,但是有很大可能使用的是不同的分配区,此阶段不会重用第一阶段留下的任何内存,并且会在新的分配区再添加 100MB 空间。造成内存暴增问题