最后我能够解决这个问题,并很乐意分享我的发现。一般来说,从我的角度来看,评估程序内存消耗的最佳工具是 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。这意味着,您将分配 4b 使用posix_memalign
但是系统会为您分配一个完整的页面以正确对齐它。如果您现在分配许多小值,您最终会得到大量未使用的内存。正常的堆分析工具不会报告此内存,因为您只分配了该内存的一小部分,但系统分配例程将分配更多并隐藏其余部分。
为了解决这个问题,我切换到更小的边界,从而可以大大减少内存开销。
作为我在 Massif & Co. 工作时间的总结,我只能推荐使用此工具进行深入分析,因为它可以让您很好地了解正在发生的事情并允许轻松跟踪错误。对于使用posix_memalign
情况不同。在某些情况下确实有必要,但是,在大多数情况下,使用普通的malloc
.