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

Как интерпретировать вывод профилировщика 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 МБ, и профайлер говорит, что это не так. Где память идет?

Спасибо за любые советы или указатели на это.

Тим

4b9b3361

Ответ 1

У меня есть теория. Моя теория заключается в том, что ваша программа использует много чего-то вроде ByteStrings. Моя теория такова, что, поскольку основное содержание ByteStrings равно malloc, они не отображаются во время профилирования. Таким образом, вы можете выбежать из кучи без того, чтобы на диаграмме профилирования отображалось самое большое содержимое вашей кучи.

Чтобы усугубить ситуацию, когда вы берете подстроки ByteStrings, они по умолчанию сохраняют указатель на первоначально выделенный блок памяти. Таким образом, даже если вы пытаетесь сохранить только небольшое количество некоторых ByteString, вы могли бы сохранить все первоначально выделенные ByteString, и это не будет отображаться в вашем профиле кучи.

Это моя теория. Я не знаю достаточно фактов о том, как работает профилировщик кучи GHC, и о том, как ByteStrings реализованы, чтобы знать наверняка. Может быть, кто-то еще может вмешаться и подтвердить или оспаривать мою теорию.

Edit2: tibbe отмечает, что буфер, используемый ByteString, закреплен. Поэтому, если вы выделяете/освобождаете множество небольших ByteString s, вы можете фрагментировать свою кучу, имея в виду, что вы исчерпали полезную кучу, причем половина ее нераспределена.

Изменить: JaffaCake сообщает мне, что иногда профилировщик кучи не отображает память, выделенную ByteStrings.

Ответ 2

Вы должны использовать, например, hp2ps, чтобы получить графическое представление о том, что происходит. Глядя на сырой файл hp трудно.

Ответ 3

Не все включено в профиль по умолчанию, например потоки и стеки. Попробуйте +RTS -xT.