Я написал компьютерное моделирование в C++, которое требует большой памяти. Он работает в итерациях, и на каждой итерации выделяется большой объем памяти, который должен быть освобожден в конце итерации. Он также использует C++ 11 реализацию <thread>
для параллельной работы.
Когда я тестирую программу на своем настольном компьютере, она работает отлично: она никогда не превышает память, которую я ей разрешаю, а во время итераций ничего не складывается. Однако, когда я отправляю программу в наш вычислительный кластер, используемая память (к которой у меня есть доступ только через программное обеспечение для очередей) растет со временем и намного превышает память, используемую на моей машине.
Позвольте мне вначале показать вам очень грубо, как структурировано программное обеспечение:
for thread in n_threads:
vector<Object> container;
for iteration in simulation:
container.clear()
container.resize(a_large_number)
... do stuff ...
Скажем, на моей машине контейнер съедает 2GB
памяти. Я вижу как в htop
и в valgrind --tool=massif
что эти 2GB
никогда не превышают. Ничто не складывается. Тем не менее, в кластере я вижу, как память растет и растет, пока она не станет намного больше, чем 2GB
(и рабочие места будут убиты/узел вычислений зависает...). Обратите внимание, что я ограничиваю количество потоков на обеих машинах и могу быть уверенным, что они равны.
Я знаю, что libc
в кластере очень старый. Чтобы скомпилировать мою программу, мне нужно было скомпилировать новую версию g++
и обновить libc
на переднем узле кластера. Программное обеспечение отлично работает на вычислительных узлах (за исключением этой проблемы с памятью), но libc там намного старше. Может ли это быть проблемой, особенно вместе с потоками, для распределения памяти? Как я могу это расследовать?
Да, в зависимости от того, сколько лет GNU libc, возможно, вам не хватает некоторых важных оптимизаций выделения памяти. Вот некоторые вещи, которые нужно попробовать (разумеется, рискуя штрафами за производительность):
Вы можете попробовать настроить malloc/free поведение через mallopt()
; используйте M_MMAP_MAX
и M_MMAP_THRESHOLD
, чтобы поощрять больше распределений проходить через mmap()
, таким образом, память, как гарантируется, будет возвращена системе после free()
.
Попробуйте сделать ваш контейнер-распределитель __gnu_cxx::malloc_allocator
, чтобы убедиться, что mallopt()
влияют на контейнер.
Попробуйте вызвать container.shrink_to_fit()
после изменения размера, чтобы убедиться, что вектор не удерживает больше памяти, чем это необходимо.