【问题标题】:Track Memory Usage in C++ and evaluate memory consumption跟踪 C++ 中的内存使用情况并评估内存消耗
【发布时间】:2012-11-09 13:17:50
【问题描述】:

我的代码遇到了以下问题:我使用 Valgrind 和 gperftools 来执行堆检查和堆分析以查看是否释放了我分配的所有内存。这些工具的输出看起来不错,似乎我并没有失去记忆。但是,当我查看 topps 的输出时,我感到很困惑,因为这基本上不代表我使用 valgrind 和 gperftools 观察到的内容。

这里是数字:

  • 热门报告:RES 150M
  • Valgrind (Massif) 报告:23M 峰值使用量
  • gperftools Heap Profiler 报告:22.7M 峰值使用率

我现在的问题是,差异从何而来?我也尝试跟踪 Valgrind 中的堆栈使用情况,但没有任何成功。

更多细节:

  • 这个过程基本上是通过 C api 将数据从 mysql 加载到内存存储中
  • 在加载完成后不久执行泄漏检查并中断,显示确定丢失 144 个字节,并且可达 10M,这符合当前分配的数量
  • 该库不执行复杂的 IPC,它启动了几个线程,但只有一个线程正在执行工作
  • 它不加载其他复杂的系统库
  • /proc/pid/smaps 中的 PSS 大小对应于 TOP 和 ps 中的 RES 大小

您有什么想法,报告的内存消耗差异来自哪里?如何验证我的程序是否正确运行?您对我如何进一步调查此问题有任何想法吗?

【问题讨论】:

  • 差异可能是由于 valgrind 本身造成的吗?
  • 我在不运行 Valgrind 或 gperftools 时收集了 RES 和 PSS 大小,所以没有。
  • RES -- Resident size (kb) The non-swapped physical memory a task has used. 我猜这是个愚蠢的问题,但是 free() 总是会减少 RES 吗?我认为另一个人也有类似问题,stackoverflow.com/questions/12262146/…

标签: c++ memory-management memory-leaks


【解决方案1】:

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

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

【讨论】:

  • 我确实检查了 Massif 和堆栈大小,如上所述,但堆栈大小恒定在 15k 左右。
【解决方案2】:

根据this 文章 ps/top 报告如果您的程序是唯一运行的程序,它使用了多少内存。假设您的程序例如使用一堆共享库,例如 STL,它们已经加载到内存中,由于程序的执行而分配的实际内存量与如果它是唯一进程将分配的内存量之间存在差距。

【讨论】:

  • 这篇文章是我从那里得到关于 PSS 大小的提示的,所以我查了一下。经过进一步调查,我现在处于内存消耗的一些差异来自底层 malloc() 实现的地步,该实现将分配更大的块和const char*std::string 转换中的一些怪异。带有--pages-as-heap=yes 选项的 Valgrinds 地块帮助很大。
  • 我明白了。只是一个进一步调查的想法:增加您在程序中分配的内存量(例如 24MB -> 512MB -> 1024MB)并观察与 top/ps 输出的未定义差异是保持不变还是还在增长。
【解决方案3】:

我终于能够解决这个问题,并且很乐意分享我的发现。一般来说,从我的角度评估程序内存消耗的最佳工具是 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 & Co 工作时间的总结。我只能推荐使用此工具进行深度剖析,因为它可以让您很好地了解正在发生的事情并允许轻松跟踪错误。对于posix_memalign的使用情况就不同了。在某些情况下确实有必要,但在大多数情况下,使用普通的malloc 就可以了。

【讨论】:

    猜你喜欢
    • 2012-07-18
    • 1970-01-01
    • 2013-01-14
    • 2011-02-19
    • 1970-01-01
    • 2011-01-18
    • 2011-05-16
    • 2018-07-01
    • 1970-01-01
    相关资源
    最近更新 更多