Подтвердить что ты не робот

Использование памяти треков в С++ и оценка потребления памяти

Я столкнулся со следующей проблемой с моим кодом: я использовал Valgrind и gperftools для выполнения проверки кучи и профилирования кучи, чтобы посмотреть, освобожу ли я всю память, которую я выделяю. Выход этих инструментов выглядит хорошо, и кажется, что я не теряю память. Однако, когда я смотрю top и вывод ps, я запутался, потому что это в основном не отражает то, что я наблюдаю с valgrind и gperftools.

Вот цифры:

  • Лучшие отчеты: RES 150M
  • Отчеты Valgrind (Massif): пиковое использование 23M
  • Отчеты gipftools Heap Profiler: пиковое использование 22.7M

Мой вопрос теперь, откуда эта разница? Я также попытался отслеживать использование стека в Valgrind, но без каких-либо успехов.

Дополнительная информация:

  • Процесс в основном загружает данные из mysql через C api в хранилище в памяти.
  • Выполнение проверки утечки и прерывания сразу после завершения загрузки показывает окончательное потеряние в 144 байта и достижимое 10M, которое соответствует сумме, которая в настоящее время выделена.
  • Библиотека не выполняет сложный IPC, она запускает несколько потоков, но только один из потоков выполняет работу.
  • Он не загружает другие сложные системные библиотеки.
  • размер PSS из /proc/pid/smaps соответствует размеру RES в TOP и ps

Есть ли у вас какие-либо идеи, откуда эта разница в потреблении памяти? Как я могу подтвердить, что моя программа ведет себя правильно? Есть ли у вас какие-либо идеи, как я мог бы продолжить исследование этой проблемы?

4b9b3361

Ответ 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.

Ответ 2

Согласно this, статья ps/top сообщает, сколько памяти использует ваша программа, если она была единственной программой. Предполагая, что ваша программа, например. использует кучу разделяемых библиотек, таких как STL, которые уже загружены в память, существует разрыв между объемом фактической памяти, которая выделяется из-за выполнения вашей программы, и количеством памяти, которое она выделила бы, если бы это был единственный процесс.

Ответ 3

По умолчанию Massif сообщает только размер кучи. TOP сообщает о фактическом размере в памяти, включая размер, используемый самим программным кодом, а также размер стека.

Попробуйте поставлять массив с опцией --stacks=yes, сообщая ему сообщать об использовании общей памяти, включая пространство стека и посмотреть, не изменилось ли это изображение?