在C++中跟踪内存使用情况并评估内存消耗

22
我在我的代码中遇到了以下问题:我正在使用 Valgrind 和 gperftools 进行堆检查和堆分析,以查看我是否释放了所有分配的内存。这些工具的输出看起来不错,似乎我没有丢失内存。然而,当我查看 top 和 ps 的输出时,我感到困惑,因为这基本上不能代表我在 valgrind 和 gperftools 中观察到的情况。
以下是数字: - Top 报告:RES 150M - Valgrind(Massif)报告:23M 峰值使用量 - gperftools 堆分析器报告:22.7M 峰值使用量
我的问题是,这种差异来自哪里?我也尝试跟踪 Valgrind 中的堆栈使用情况,但没有任何成功的结果。
更多细节如下: - 进程基本上通过 C API 从 mysql 加载数据到内存存储中 - 在加载完成后执行泄漏检查并很快中断,显示明确的 144 字节丢失和 10M 可达性,这符合当前分配的数量 - 库不执行复杂的 IPC,它启动了一些线程,但只有一个线程正在执行工作 - 它不加载其他复杂的系统库 - /proc/pid/smaps 中的 PSS 大小与 TOP 和 ps 中的 RES 大小相对应
你有任何想法,这种报告的存储器消耗差异来自哪里吗?我该如何确认我的程序表现正确?你有任何想法如何进一步调查这个问题吗?

1
可能是由于Valgrind本身造成的差异吗? - Karthik T
1
我在不运行Valgrind或gperftools时收集了RES和PSS大小,因此不是。 - grundprinzip
1
RES -- 常驻内存大小(kb) 任务使用的未交换物理内存。这可能是一个愚蠢的问题,但是free()函数是否总是会减少RES?我认为还有另外一个人遇到了类似的问题,可以在http://stackoverflow.com/questions/12262146/free-can-not-release-the-mem-and-decrease-the-value-of-res-column-of-top找到答案。 - Karthik T
3个回答

22

最终,我成功解决了这个问题,很高兴能分享我的发现。根据我的经验,评估程序内存消耗的最佳工具是来自Valgrind的Massif 工具。它可以让您对堆空间使用进行剖析并提供详细分析。

若要对应用程序的堆进行剖析,请运行valgrind --tool=massif prog,这将为您提供关于典型的内存分配函数(如malloc和相关函数)的基本访问权限。但是,为了深入挖掘,我激活了选项--pages-as-heap=yes,它会报告有关底层系统调用的信息。以下是我的剖析会话中的一个示例:

 67  1,284,382,720      978,575,360      978,575,360             0            0
100.00% (978,575,360B) (page allocation syscalls) mmap/mremap/brk, --alloc-fns, etc.
->87.28% (854,118,400B) 0x8282419: mmap (syscall-template.S:82)
| ->84.80% (829,849,600B) 0x821DF7D: _int_malloc (malloc.c:3226)
| | ->84.36% (825,507,840B) 0x821E49F: _int_memalign (malloc.c:5492)
| | | ->84.36% (825,507,840B) 0x8220591: memalign (malloc.c:3880)
| | |   ->84.36% (825,507,840B) 0x82217A7: posix_memalign (malloc.c:6315)
| | |     ->83.37% (815,792,128B) 0x4C74F9B: std::_Rb_tree_node<std::pair<std::string const, unsigned int> >* std::_Rb_tree<std::string, std::pair<std::string const, unsigned int>, std::_Select1st<std::pair<std::string const, unsigned int> >, std::less<std::string>, StrategizedAllocator<std::pair<std::string const, unsigned int>, MemalignStrategy<4096> > >::_M_create_node<std::pair<std::string, unsigned int> >(std::pair<std::string, unsigned int>&&) (MemalignStrategy.h:13)
| | |     | ->83.37% (815,792,128B) 0x4C7529F: OrderIndifferentDictionary<std::string, MemalignStrategy<4096>, StrategizedAllocator>::addValue(std::string) (stl_tree.h:961)
| | |     |   ->83.37% (815,792,128B) 0x5458DC9: var_to_string(char***, unsigned long, unsigned long, AbstractTable*) (AbstractTable.h:341)
| | |     |     ->83.37% (815,792,128B) 0x545A466: MySQLInput::load(std::shared_ptr<AbstractTable>, std::vector<std::vector<ColumnMetadata*, std::allocator<ColumnMetadata*> >*, std::allocator<std::vector<ColumnMetadata*, std::allocator<ColumnMetadata*> >*> > const*, Loader::params const&) (MySQLLoader.cpp:161)
| | |     |       ->83.37% (815,792,128B) 0x54628F2: Loader::load(Loader::params const&) (Loader.cpp:133)
| | |     |         ->83.37% (815,792,128B) 0x4F6B487: MySQLTableLoad::executePlanOperation() (MySQLTableLoad.cpp:60)
| | |     |           ->83.37% (815,792,128B) 0x4F8F8F1: _PlanOperation::execute_throws() (PlanOperation.cpp:221)
| | |     |             ->83.37% (815,792,128B) 0x4F92B08: _PlanOperation::execute() (PlanOperation.cpp:262)
| | |     |               ->83.37% (815,792,128B) 0x4F92F00: _PlanOperation::operator()() (PlanOperation.cpp:204)
| | |     |                 ->83.37% (815,792,128B) 0x656F9B0: TaskQueue::executeTask() (TaskQueue.cpp:88)
| | |     |                   ->83.37% (815,792,128B) 0x7A70AD6: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.16)
| | |     |                     ->83.37% (815,792,128B) 0x6BAEEFA: start_thread (pthread_create.c:304)
| | |     |                       ->83.37% (815,792,128B) 0x8285F4B: clone (clone.S:112)
| | |     |                         
| | |     ->00.99% (9,715,712B) in 1+ places, all below ms_print's threshold (01.00%)
| | |     
| | ->00.44% (4,341,760B) in 1+ places, all below ms_print's threshold (01.00%)

正如您所看到的,我大约85%的内存分配来自于单个分支,现在问题是为什么内存消耗如此之高,如果原始堆分析显示正常消耗。如果您查看示例,您将会了解原因。为了分配,我使用了posix_memalign来确保分配发生在有用的边界上。然后将该分配器从外部类传递给内部成员变量(在本例中为映射)以用于堆分配。然而,我选择的边界太大了 - 4096 - 在我的情况下。这意味着,您将使用posix_memalign分配4b,但系统将为您分配整个页面以正确对齐它。如果您现在分配许多小值,则会产生大量未使用的内存。这些内存不会被常规堆分析工具报告,因为您只分配了其中一部分内存,但系统分配例程将分配更多并隐藏其余部分。

为了解决这个问题,我切换到了更小的边界,从而能够显著减少内存开销。

作为我花费在Massif和其他工具前的时间的结论,我只能建议使用这个工具进行深度分析,因为它可以让您非常好地了解发生了什么,并轻松跟踪错误。对于使用posix_memalign的情况则略有不同。有些情况下确实需要使用它,但对于大多数情况,使用普通的malloc就可以了。


2
根据这篇文章,ps/top报告了如果您的程序是唯一运行的程序,它将使用多少内存。假设您的程序使用一堆共享库(如STL),这些库已经加载到内存中,那么实际分配给您的程序的内存量与它作为唯一进程分配的内存量之间存在差距。请注意保留HTML标签。

这篇文章是我了解 PSS 大小的窍门,所以我进行了检查。通过进一步调查,我发现部分内存消耗差异来自底层 malloc() 实现将分配更大块和一些奇怪的 const char* 转换为 std::string 的问题。Valgrind 的 massif 选项 --pages-as-heap=yes 对此有很大帮助。 - grundprinzip
我明白了。进一步调查的一个想法是:增加程序内分配的内存量(例如24MB->512MB->1024MB),观察与top/ps输出之间未定义的差异是否保持恒定或者也在增长。 - Christian

0

默认情况下,Massif仅报告堆大小。TOP报告实际内存大小,包括程序代码本身使用的大小以及堆栈大小。

尝试使用--stacks=yes选项提供Massif,告诉它报告总内存使用情况,包括堆栈空间,并查看是否会改变情况?


我已经使用Massif检查了堆栈大小,如上所述,但是堆栈大小大约在15k左右保持不变。 - grundprinzip

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接