Ответ 1
Наконец, я смог решить проблему и с удовольствием поделился своими выводами. В общем, лучшим инструментом для оценки потребления памяти программы с моей точки зрения является инструмент Massif от Valgrind. он позволяет вам профилировать потребление кучи и дает подробный анализ.
Теперь, чтобы просмотреть кучу вашего приложения 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 threshold (01.00%)
| | |
| | ->00.44% (4,341,760B) in 1+ places, all below ms_print threshold (01.00%)
Как вы видите, ~ 85% моего распределения памяти поступает из одной ветки, и теперь возникает вопрос, почему потребление памяти настолько велико, если исходное профилирование кучи показало нормальное потребление. Если вы посмотрите на пример, вы поймете, почему. Для распределения я использовал posix_memalign
, чтобы убедиться, что распределения происходят с полезными границами. Затем этот распределитель передавался из внешнего класса во внутренние переменные-члены (в этом случае карта), чтобы использовать распределитель для распределения кучи. Однако граница, которую я выбрал, была слишком большой - 4096 - в моем случае. Это означает, что вы выделите 4b с помощью posix_memalign
, но система выделит полную страницу, чтобы вы могли правильно ее выровнять. Если вы теперь выделите много небольших значений, вы получите много неиспользуемой памяти. Эта память не будет сообщаться с помощью обычных инструментов профилирования кучи, поскольку вы выделяете только часть этой памяти, но процедуры выделения системы будут выделять больше и скрывать остальные.
Чтобы решить эту проблему, я переключился на меньшую границу и, таким образом, мог значительно уменьшить накладные расходы памяти.
Как вывод моих часов, проведенных перед Massif и Co., я могу рекомендовать использовать этот инструмент для глубокого профилирования, поскольку он дает вам очень хорошее представление о том, что происходит, и позволяет легко отслеживать ошибки. Для использования posix_memalign
ситуация другая. Бывают случаи, когда это действительно необходимо, однако в большинстве случаев вы просто будете нормально с нормальным malloc
.