Как получить контроль над кучей 5 ГБ в Haskell?

В настоящее время я экспериментирую с небольшим веб-сервером Haskell, написанным на Snap, который загружает и предоставляет клиенту большой объем данных. И мне очень и очень трудно получить контроль над серверным процессом. В случайные моменты процесс использует много ресурсов ЦП от секунд до минут и перестает отвечать на запросы клиентов. Иногда использование памяти резко возрастает (а иногда и падает) на сотни мегабайт за секунды.

Надеюсь, у кого-то будет больше опыта с длительными процессами Haskell, которые используют много памяти, и он может дать мне несколько указателей, чтобы сделать это более стабильным. Я отлаживаю эту штуку уже несколько дней, и здесь я начинаю немного отчаяться.

Небольшой обзор моей настройки:

  • При запуске сервера я прочитал около 5 гигабайт данных в большом (вложенном Данные - структура в памяти, похожая на карту. Вложенная карта имеет строгие значения, и все значения внутри карты имеют типы данных, и все их поля также являются строгими. Я потратил много времени на то, чтобы не осталось недооцененных переходов. Импорт (в зависимости от загрузки моей системы) занимает около 5-30 минут. Странно то, что колебания в последовательных запусках намного больше, чем я ожидал, но это другая проблема.

  • Большая структура данных живет внутри «TVar», который используется всеми клиентскими потоками, порожденными сервером Snap. Клиенты могут запрашивать произвольные части данных, используя небольшой язык запросов. Объем запроса данных обычно невелик (до 300 КБ или около того) и затрагивает только небольшую часть структуры данных. Все запросы только для чтения выполняются с использованием readTVarIO,поэтому они не требуют никаких транзакций STM.

  • Сервер запускается со следующими флагами: + RTS -N -I0 -qg -qb. Это запустит сервер в многопоточном режиме, отключит простой и параллельный сборщик мусора. Похоже, это значительно ускоряет процесс.

Сервер в основном работает без проблем. Однако время от времени время ожидания клиентского запроса истекает, и ЦП достигает 100% (или даже более 100%) и продолжает делать это в течение длительного времени. Между тем, сервер больше не отвечает на запрос.

Я могу придумать несколько причин, которые могут вызвать загрузку ЦП:

  • Запрос просто занимает много времени, потому что необходимо выполнить много работы. Это в некоторой степени маловероятно, потому что иногда это происходит с запросами, которые оказались очень быстрыми в предыдущих запусках (с быстрым, я имею в виду 20-80 мс или около того).

  • Все еще есть некоторые неоцененные преобразователи, которые необходимо вычислить, прежде чем данные могут быть обработать и отправить клиенту. Это также маловероятно по той же причине, что и в предыдущем пункте.

  • Каким-то образом срабатывает сборка мусора и начинает сканировать всю мою кучу размером 5 ГБ. Я могу представить, что это может занять много времени.

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

Я лично подозреваю, что проблема заключается в сборке мусора. Я использую GHC7, у которого, кажется, есть много возможностей для настройки работы GC.

Какие настройки GC вы рекомендуете при использовании больших куч с очень стабильными данными?

48
задан Sebastiaan Visser 8 July 2011 в 12:36
поделиться