Чтобы создать исполняемую программу, система GHC компилирует ваш код, а затем связывает его со сложной системой времени выполнения, которая управляет памятью, планированием потоков, профилированием и так далее.
У RTS есть множество параметров, позволяющих управлять её поведением. Например, можно изменить интервал переключения контекста, размер кучи по умолчанию и включить профилирование кучи. Эти параметры можно передавать системе времени выполнения разными способами; в следующем разделе (Задание параметров RTS) описаны различные способы, а в последующих разделах — сами параметры RTS.
5.7.1. Задание параметров RTS
Есть четыре способа задать параметры RTS:
- в командной строке между
+RTS ... -RTSпри запуске программы (Задание параметров RTS в командной строке) - во время компиляции с помощью
-with-rtsopts=⟨opts⟩(Задание параметров RTS во время компиляции) - с помощью переменной окружения
GHCRTS(Задание параметров RTS с помощью переменной окружения GHCRTS) - переопределив «хуки» в системе времени выполнения («Хуки» для изменения поведения RTS)
5.7.1.1. Задание параметров RTS в командной строке
Если при компоновке задать флаг -rtsopts[=⟨none|some|all|ignore|ignoreAll⟩] нужным образом (см. Параметры, влияющие на компоновку), можно передавать параметры RTS в командной строке при запуске программы.
При запуске программы на Haskell RTS извлекает для себя аргументы командной строки, заключённые между +RTS и -RTS. Например:
$ ghc prog.hs -rtsopts [1 of 1] Compiling Main ( prog.hs, prog.o ) Linking prog ... $ ./prog -f +RTS -H32m -S -RTS -h foo bar
RTS заберёт себе -H32m -S, а оставшиеся аргументы -f -h foo bar будут доступны программе, если/когда она вызовет System.Environment.getArgs.
Параметр -RTS не требуется, если параметры системы времени выполнения занимают конец командной строки, как в этом примере:
% hls -ltr /usr/etc +RTS -A5m
Если вы совершенно точно хотите, чтобы все оставшиеся параметры командной строки передавались программе, а не RTS, используйте --RTS или --. Разница в том, что --RTS не будет передан программе, а -- будет.
Как обычно, для параметров RTS, принимающих значения ⟨size⟩: если последний символ в ⟨size⟩ — K или k, значение умножается на 1024; если M или m — на 1024*1024; если G или G — на 1024^3. (И за любое переполнение счётчиков отвечаете вы!)
Передача параметра RTS +RTS -? выведет список параметров RTS, доступных в вашей программе (он различается в зависимости от способа компиляции).
Примечание
Поскольку GHC сама скомпилирована с помощью GHC, можно изменять параметры RTS компилятора, используя обычное сочетание +RTS ... -RTS. Например, чтобы установить максимальный размер кучи для компиляции равным 128M, добавьте +RTS -M128m -RTS в командную строку.
5.7.1.2. Задание параметров RTS во время компиляции
GHC позволяет изменять параметры RTS по умолчанию для программы во время компиляции с помощью флага -with-rtsopts (Параметры, влияющие на компоновку). Часто это используют, чтобы задать для программы размер кучи и/или стека по умолчанию, превышающий стандартный. Например, чтобы установить -H128m -K64m, выполните компоновку с помощью -with-rtsopts="-H128m -K64m".
5.7.1.3. Задание параметров RTS с помощью переменной окружения GHCRTS
-
GHCRTS -
Если при компоновке флаг
-rtsoptsзадан значением, отличным отnoneилиignoreAll, параметры RTS также берутся из переменной окруженияGHCRTS. Например, чтобы установить максимальный размер кучи равным 2G для всех программ, скомпилированных GHC (в оболочке, подобнойsh):GHCRTS='-M2G' export GHCRTS
Параметры RTS, полученные из переменной окружения
GHCRTS, можно переопределить параметрами, заданными в командной строке.
Совет
Задание в окружении чего-то вроде GHCRTS=-M2G — удобный способ не допустить, чтобы программы на Haskell потребляли больше реальной памяти, чем есть на вашем компьютере. Это легко может произойти случайно и привести к тому, что компьютер будет работать крайне медленно, пока ОС не решит завершить процесс (и остаётся надеяться, что она завершит нужный).
5.7.1.4. «Хуки» для изменения поведения RTS
GHC позволяет в некоторой степени управлять отдельными параметрами RTS для любой программы, добавив при компиляции «хук», который вызывается системой времени выполнения. В RTS есть заглушки для этих хуков, но можно переопределить их стандартные реализации, написав собственную версию и добавив её при компоновке в командной строке GHC.
Из-за особенностей компоновки DLL эти хуки не работают в Windows, если программа собрана динамически.
5.7.1.4.1. События времени выполнения
Можно изменить сообщения, выводимые при «сбое» системы времени выполнения, например при переполнении стека. Для этого предусмотрены следующие хуки:
-
void OutOfHeapHook(unsigned long, unsigned long) -
Сообщение о переполнении кучи.
-
void StackOverflowHook(long int) -
Сообщение о переполнении стека.
-
void MallocFailHook(long int) -
Сообщение, выводимое при сбое
malloc.
5.7.1.4.2. Вывод журнала событий
Кроме того, GHC позволяет задать способ записи данных журнала событий (см. -l
⟨flags⟩) с помощью пользовательского EventLogWriter:
-
type size_t -
- Скрыто:
-
type EventLogWriter -
Получатель данных журнала событий.
-
void initEventLogWriter(void) -
Инициализирует ваш
EventLogWriter. Этот метод необязателен.
-
bool writeEventLog(void *eventlog, size_t eventlog_size) -
Передаёт буферизованные данные журнала событий вашему обработчику журнала. Возвращает true в случае успеха. Обязателен для пользовательского
EventLogWriter.Обратите внимание: эта функция может одновременно вызываться несколькими потоками.
-
void flushEventLog(void) -
Сбрасывает буферы (если они есть) вашего пользовательского
EventLogWriter. Этот метод может бытьNULL.Обратите внимание: эта функция может одновременно вызываться несколькими потоками.
-
void stopEventLogWriter(void) -
Вызывается перед остановкой ведения журнала событий. Этот метод может быть
NULL.
-
Для использования EventLogWriter API RTS предоставляет следующие функции:
-
EventLogStatus eventLogStatus(void) -
Проверяет, поддерживает ли текущая система времени выполнения журнал событий (например, была ли текущая исполняемая программа скомпонована с
-eventlog), и, если поддерживает, ведётся ли в данный момент запись.
-
bool startEventLogging(const EventLogWriter *writer) -
Запускает запись событий с помощью указанного
EventLogWriter. Возвращает true в случае успеха или false, если другой обработчик уже был настроен.
-
void endEventLogging() -
Останавливает активный
EventLogWriter.
где enum EventLogStatus — это:
-
type EventLogStatus -
-
EVENTLOG_NOT_SUPPORTED: система времени выполнения скомпилирована без поддержки журнала событий. -
EVENTLOG_NOT_CONFIGURED:EventLogWriterещё не настроен. -
EVENTLOG_RUNNING:EventLogWriterнастроен и работает.
-
5.7.2. Прочие параметры RTS
-
--install-signal-handlers=⟨yes|no⟩ -
Если задано значение yes (по умолчанию), RTS устанавливает обработчики сигналов для перехвата таких событий, как Ctrl-C. Этот параметр полезен главным образом при использовании кода Haskell в виде DLL, когда вы хотите установить собственные обработчики сигналов.
Обратите внимание: даже при
--install-signal-handlers=noсигнал интервального таймера RTS остаётся включённым. В зависимости от конфигурации RTS и возможностей ОС используется сигнал таймера SIGVTALRM или SIGALRM. Чтобы отключить сигнал таймера, используйте параметр RTS-V0(см.-V ⟨secs⟩).
-
--install-seh-handlers=⟨yes|no⟩ -
Если задано значение yes (по умолчанию), RTS в Windows устанавливает обработчики исключений для перехвата необработанных исключений с помощью механизма обработки исключений Windows. Этот параметр полезен главным образом при использовании кода Haskell в виде DLL, если вы не хотите, чтобы RTS аварийно завершала приложение при таких ошибках, как ошибки сегментации.
-
--generate-crash-dumps -
Если задано значение yes (по умолчанию), RTS в Windows создаёт дамп памяти при любом аварийном завершении. Эти дампы можно изучать с помощью отладчиков, таких как WinDBG. В дампах сохраняются все данные кода, регистров и потоков на момент сбоя. Обратите внимание: для этого требуется
--install-seh-handlers=yes.
-
--generate-stack-traces=<yes|no> -
Если задано значение yes (по умолчанию), RTS в Windows при аварийном завершении создаёт трассировку стека, если включена обработка исключений. Чтобы получить больше информации в скомпилированных исполняемых файлах, должны быть доступны символы C-кода или DLL.
-
--disable-delayed-os-memory-return -
Если задан, использует
MADV_DONTNEEDвместоMADV_FREEна платформах, где это позволяет точнее отображать фактический объём резидентной памяти, используемой программой, в средствах мониторинга памяти (например, в столбцеRSSвtopиhtop).Ожидается, что это немного замедлит работу программы.
В Linux MADV_FREE новее и быстрее, поскольку позволяет не обнулять страницы, если процесс повторно использует их позднее (см.
man 2 madvise). Однако средства анализа памяти, такие какtop, не сразу отражают освобождение памяти в отображаемом объёме резидентной памяти (столбец RSS): только при нехватке памяти Linux фактически удаляет освобождённые страницы из процесса и обновляет статистику RSS. До этого момента страницы отображаются какLazyFreeв/proc/PID/smaps(см.man 5 proc).Задержка обновления RSS может сбивать с толку разработчиков, отлаживающих проблемы с памятью, средства мониторинга памяти в рабочих средах и конечных пользователей, которые могут жаловаться на чрезмерное использование памяти, отображаемое в средствах мониторинга. Этот флаг позволяет отключить такую задержку.
-
--io-manager=(name) -
Выбирает используемый диспетчер ввода-вывода. В некоторых сочетаниях платформы и режима RTS (с потоками или без них) доступно несколько реализаций диспетчера ввода-вывода. Этот флаг позволяет переопределить вариант по умолчанию и выбрать реализацию по имени.
В настоящее время доступны следующие диспетчеры ввода-вывода:
Имя
Платформы
Режим RTS
selectPosix
Без потоков
mioВсе
С потоками
win32-legacyWindows
Без потоков
winioWindows
Любой
-
-xp -
На 64-разрядных машинах компоновщику времени выполнения обычно необходимо отображать объектный код в нижних 2 ГБ адресного пространства. Это связано с малой моделью памяти x86_64, в которой большинство ссылок на символы имеют разрядность 32 бита. Проблема в том, что эти 2 ГБ адресного пространства могут заполниться, особенно если в GHCi загружается очень большое количество объектных файлов.
Этот флаг предоставляет обходное решение, хотя и несколько сложное. Чтобы иметь возможность загружать объектный файл за пределами нижних 2 ГБ, объектный код необходимо скомпилировать с
-fPIC -fexternal-dynamic-refs. Если передан флаг+RTS -xp, компоновщик предполагает, что все объектные файлы скомпилированы с-fPIC -fexternal-dynamic-refs, и загружает их в любую область адресного пространства. Вы должны самостоятельно позаботиться о том, чтобы загружаемые объектные файлы (включая все пакеты) были скомпилированы нужным образом. Если это не так, компоновщик, скорее всего, завершится с сообщением об ошибке, обнаружив проблему.На некоторых платформах, где PIC используется всегда, например в macOS и OpenBSD на x86_64, а также в macOS и Linux на aarch64, этот флаг включён по умолчанию. Из этого следует, что используемые системные библиотеки тоже должны быть скомпилированы с
-fPIC, если их требуется загружать компоновщиком времени выполнения.
-
-xm ⟨address⟩ -
Предупреждение
Этот параметр предназначен только для обхода проблем с выделением памяти. Не используйте его, если только GHCi не завершается с сообщением вроде «
failed to mmap() memory below 2Gb». Рассмотрите возможность перекомпиляции объектов с-fPIC -fexternal-dynamic-refsи использования вместо этого флага-xp. Если для работы GHCi на вашей машине вам приходится использовать этот параметр, сообщите об ошибке.На 64-разрядных машинах RTS необходимо выделять память в нижних 2 ГБ адресного пространства. Поддержка этого механизма различается в разных операционных системах и иногда работает некорректно. Этот параметр позволяет подсказать RTS, где следует выделять память в нижних 2 ГБ адресного пространства. Например,
+RTS -xm20000000 -RTSподскажет RTS начинать выделение с отметки 0,5 ГБ. По умолчанию используется встроенная поддержка ОС для выделения памяти в нижних 2 ГБ, если она доступна (например,mmapсMAP_32BITв Linux); в противном случае используется-xm40000000.
-
-xq ⟨size⟩ -
- По умолчанию:
-
100k
Этот параметр относится к ограничениям на выделение памяти; подробнее об этом см. в разделе GHC.Conc.enableAllocationLimit. Когда поток достигает своего лимита выделения памяти, RTS выбрасывает исключение в этом потоке, после чего поток получает дополнительную квоту на выделение памяти до повторного выброса исключения. Это позволяет потоку выполнить обработчики исключений. Параметр
-xqзадаёт размер этой дополнительной квоты.
-
-xr ⟨size⟩ -
- По умолчанию:
-
1T
Этот параметр задаёт размер виртуального адресного пространства, резервируемого двухэтапным распределителем на 64-разрядной платформе. Он может быть полезен в ситуациях, когда даже резервирование большого диапазона адресов без фиксации выделенной памяти обходится дорого (например, в WSL1), или когда физической памяти достаточно и требуется поддержка кучи Haskell размером более 1T.
-xrничего не делает, если GHC настроена с--disable-large-address-spaceили если платформа 32-разрядная.
-
--optimistic-linking -
Если задан, указывает компоновщику времени выполнения продолжать компоновку при наличии неразрешённого символа.