Как интерпретировать вывод профилировщика ghc heap?
У меня есть серверный процесс, реализованный в haskell, который действует как простой двоичный файл in-memory. Клиентские процессы могут подключаться, а затем добавлять и извлекать данные. Служба использует больше памяти, чем я ожидал бы, и я пытаюсь понять, почему.
Самая грубая метрика у меня есть linux "top". Когда я запускаю процесс, я вижу размер изображения "VIRT" размером ~ 27 МБ. После запуска клиента для вставки 60 000 элементов данных я вижу размер изображения ~ 124 МБ.
Запуск процесса для сбора статистики GC (+ RTS -S), я вижу первоначально
Alloc Copied Live GC GC TOT TOT Page Flts
bytes bytes bytes user elap user elap
28296 8388 9172 0.00 0.00 0.00 0.32 0 0 (Gen: 1)
и при добавлении элементов 60k я вижу, что живые байты плавно растут до
...
532940 14964 63672180 0.00 0.00 23.50 31.95 0 0 (Gen: 0)
532316 7704 63668672 0.00 0.00 23.50 31.95 0 0 (Gen: 0)
530512 9648 63677028 0.00 0.00 23.50 31.95 0 0 (Gen: 0)
531936 10796 63686488 0.00 0.00 23.51 31.96 0 0 (Gen: 0)
423260 10047016 63680532 0.03 0.03 23.53 31.99 0 0 (Gen: 1)
531864 6996 63693396 0.00 0.00 23.55 32.01 0 0 (Gen: 0)
531852 9160 63703536 0.00 0.00 23.55 32.01 0 0 (Gen: 0)
531888 9572 63711876 0.00 0.00 23.55 32.01 0 0 (Gen: 0)
531928 9716 63720128 0.00 0.00 23.55 32.01 0 0 (Gen: 0)
531856 9640 63728052 0.00 0.00 23.55 32.02 0 0 (Gen: 0)
529632 9280 63735824 0.00 0.00 23.56 32.02 0 0 (Gen: 0)
527948 8304 63742524 0.00 0.00 23.56 32.02 0 0 (Gen: 0)
528248 7152 63749180 0.00 0.00 23.56 32.02 0 0 (Gen: 0)
528240 6384 63756176 0.00 0.00 23.56 32.02 0 0 (Gen: 0)
341100 10050336 63731152 0.03 0.03 23.58 32.35 0 0 (Gen: 1)
5080 10049728 63705868 0.03 0.03 23.61 32.70 0 0 (Gen: 1)
Кажется, это говорит мне, что куча имеет ~ 63 МБ живых данных. Это может быть совместимо с числами сверху, к тому времени, когда вы добавляете в пространство стека, кодовое пространство, накладные расходы GC и т.д.
Итак, я попытался использовать профилировщик кучи, чтобы выяснить, что составляет
это 63 МБ. Результаты сбивают с толку. Запуск с помощью "+ RTS -h" и просмотр
сгенерированный hp файл, последний и самый большой моментальный снимок имеет:
containers-0.3.0.0:Data.Map.Bin 1820400
bytestring-0.9.1.7:Data.ByteString.Internal.PS 1336160
main:KV.Store.Memory.KeyTree 831972
main:KV.Types.KF_1 750328
base:GHC.ForeignPtr.PlainPtr 534464
base:Data.Maybe.Just 494832
THUNK 587140
Все остальные числа в снимке намного меньше этого.
Добавление этих значений дает пиковое использование памяти как ~ 6 МБ, что отражено в
вывод графика:
![enter image description here]()
Почему это не соответствует живым байтам, как показано в статистике GC? Это
трудно понять, как мои структуры данных могут потребовать 63 МБ, и
профайлер говорит, что это не так. Где память идет?
Спасибо за любые советы или указатели на это.
Тим
Ответы
Ответ 1
У меня есть теория. Моя теория заключается в том, что ваша программа использует много чего-то вроде ByteStrings
. Моя теория такова, что, поскольку основное содержание ByteStrings
равно malloc
, они не отображаются во время профилирования. Таким образом, вы можете выбежать из кучи без того, чтобы на диаграмме профилирования отображалось самое большое содержимое вашей кучи.
Чтобы усугубить ситуацию, когда вы берете подстроки ByteStrings
, они по умолчанию сохраняют указатель на первоначально выделенный блок памяти. Таким образом, даже если вы пытаетесь сохранить только небольшое количество некоторых ByteString
, вы могли бы сохранить все первоначально выделенные ByteString
, и это не будет отображаться в вашем профиле кучи.
Это моя теория. Я не знаю достаточно фактов о том, как работает профилировщик кучи GHC, и о том, как ByteStrings
реализованы, чтобы знать наверняка. Может быть, кто-то еще может вмешаться и подтвердить или оспаривать мою теорию.
Edit2: tibbe отмечает, что буфер, используемый ByteString
, закреплен. Поэтому, если вы выделяете/освобождаете множество небольших ByteString
s, вы можете фрагментировать свою кучу, имея в виду, что вы исчерпали полезную кучу, причем половина ее нераспределена.
Изменить: JaffaCake сообщает мне, что иногда профилировщик кучи не отображает память, выделенную ByteStrings.
Ответ 2
Вы должны использовать, например, hp2ps, чтобы получить графическое представление о том, что происходит. Глядя на сырой файл hp трудно.
Ответ 3
Не все включено в профиль по умолчанию, например потоки и стеки. Попробуйте +RTS -xT
.