Система
LomentGC
Когда программа на Loment освобождает память, она не отдаёт всю работу сборщику. Каждое место, где происходит выделение, приписывается во время компиляции к одной из четырёх стратегий — а сколько остаётся сборщику этапа выполнения, это число, записанное в артефакт, а не утверждение в документе.
Лестница
От сильнейшего к слабейшему: компилятор берёт самую сильную ступень, какую может доказать. Докажешь больше — платишь меньше. Цена каждой ступени написана рядом с ней, а не оставлена в сноске.
L0 — статическая
Размер выделения — литерал, и указатель всегда передаётся целиком как первый аргумент загрузки, записи или атомарной операции. Он вообще не доходит до кучи, так что возвращать нечего. Цена: более крупный кадр стека.
L1 — последнее использование
Та же форма, объявленная на верхнем уровне тела функции, поэтому последнее
использование исчислимо. Компилятор вставляет free перед
самым ранним возвратом после него. Цена: на пути с ранним возвратом
точка вставки может быть пропущена, и этот путь течёт один раз. Утечка, а не ошибка.
L2 — эпохи блоков
Внутри тела цикла каждое выделение — локальное, которое никогда не покидает тело. Конец тела возвращает всю область разом, откатывая фронт аллокатора назад — без списка, без сканирования. Эпоха выводится из структуры блоков и выхода за их пределы, а не пишется вручную. Цена: если одно выделение в теле уходит наружу, весь блок должен остаться.
L3 — этап выполнения
Всё, что компилятор не смог доказать. Полная цена сборщика.
Три уровня
Программа выбирает, как далеко вниз по лестнице она готова пойти, одной строкой в корневом модуле.
choose gc_manual
Значение по умолчанию. Ничего не возвращается автоматически; программа управляет своей памятью сама. Если не написать ни строки, получите именно это — значение по умолчанию есть поведение, которое было до появления измерения, и это правило соблюдает каждое измерение.
choose gc_auto
Сборщик этапа выполнения возвращает память: маркировка и очистка с точными корнями, запускаемая по числу выделенных байт.
choose gc_auto_alpha
Включены все четыре ступени, поэтому сборщик видит только то, что не смогли доказать ступени времени компиляции, — и он адаптивный: порог следует за живым набором, с триггером по пространству, так что потолок — не фиксированная доля арены.
Предел, который назван, а не обнаружен. При gc_auto
триггер — фиксированные 32 KiB, тогда как арена — 64 KiB, поэтому программа, чей
живой набор превышает примерно 32 KB, всё равно исчерпает память. Закрыть этот
разрыв — то, для чего нужны адаптивный сборщик и поколенческий под
gc_auto_alpha.
Внутри L3
Ступень этапа выполнения — тоже не один алгоритм. Она росла в три шага, и каждый должен был обойти предыдущий на измеримой программе.
Маркировка и очистка
Корни точные — таблица корней хранит адреса слотов, — тогда как внутренность
блока трактуется консервативно, потому что alloc нетипизирован и ничто
не записывает, что хранит блок.
Адаптивный запуск
Фиксированному порогу приходится угадывать, насколько большим станет живой набор. Адаптивный его читает: собирать, когда живой набор удвоился, или когда пространство на исходе.
Поколенческий
Два поколения, разрезанных по адресу, а не по возрасту объектов: неперемещающий сборщик повышает в конце каждой малой сборки, продвигая фронтир. Таблица карточек и барьер записи ловят указатели из старого в молодое. Что это экономит — проход маркировки и распространения, который затем покрывает только молодое поколение.
Почему это попадает в артефакт
Каждый модуль экспортирует форму-объект, несущий gc_ladder: сколько
участков alloc взяла каждая ступень, плюс всего. Четыре счётчика должны в
сумме давать итог, и два независимых валидатора проверяют эту арифметику, не
читая ни строки исходника. Так что «как управляется память этой программы?» —
это число в выводе сборки, а не утверждение в документе.
Что утверждается, а что нет. Лестница реализована дважды — эталонным компилятором и самоподдерживающимся (self-hosted) — и эти двое должны совпадать побайтово. Измерения за ступенями идут из собственных критериев проекта, а это небольшие программы; сборщик не запускался на реальной нагрузке, и это стоит знать, прежде чем читать всё это как утверждение о производительности.