Carat::BuildPool
Inherits Reference < Object
Параллельные crystal build масштабируются ~N по wall-clock (один
процесс — фактически одно ядро на кодоген), но каждый компилятор
держит ~1-2 ГБ RAM, поэтому фиксированная ширина пула, подобранная
под одну машину, на другой уходит в OOM. Вместо этого ширина
выводится из ТЕКУЩЕГО состояния машины (Sysinfo): ядра ограничивают
сверху, MemAvailable / замеренный peak RSS компилятора — снизу:
workers = min(ядра, MemAvailable * 0.7 / peak_rss, 8, |jobs|)
Peak RSS не угадывается, а измеряется: первая сборка греет слот j0 кэша компилятора, пока сэмплируется RSS её процесса (carat + все дети — crystal, cc); слот затем клонируется в пустые слоты воркеров (паттерн из Rakefile egui.cr, обобщённый). Перед выдачей каждого следующего задания гейт перепроверяет MemAvailable и придерживает очередь, когда свободной памяти меньше (peak + запас) — случай «один бинарник тяжелее остальных» замедляет пул, а не валит его. Сборка, всё-таки умершая от OOM (exit 137 / SIGKILL), после осушения пула ретраится один раз последовательно.
Конкурентные компиляторы также гоняются на общем ~/.cache/crystal (tmp-переименования), поэтому каждый воркер получает свой персистентный CRYSTAL_CACHE_DIR: <корень шарда>/.carat/cache/jN — он живёт между запусками, второй и дальнейшие прогоны тёплые. CARAT_JOBS=N отключает адаптивный подбор (бенчмарки, A/B-прогоны).
Constants
Пока warm-up не измерил реальный след: ~2 ГБ на release-сборку масштаба egui-cr.
Запас свободной RAM ниже наблюдаемого peak RSS (гейт очереди).
(одиночные _i64-литералы: 512 * 1024_i64 в Crystal выводится как
Int32 из-за сворачивания литералов)
Пол измеренного peak: крошечная сборка не должна раздувать пул до 8 воркеров (она и так собирается секунды — ширина не важна).
Constructors
Class methods
Запуск crystal с env-переопределениями (CRYSTAL_CACHE_DIR) —
вывод наследуется, окружение = текущее + overrides.