Сначала выводы
- Ускорение в 3 раза даёт выделенный bare metal M4 + прогретый DerivedData + параллельные узлы, а не дополнительные флаги компилятора.
- Локальный MacBook — нулевой порог входа; GitHub Actions — эластичность; общий Mac VPS — дёшево, но без тёплого контекста; аренда bare metal переносит локальный опыт на оплачиваемые runner'ы.
- Сравнительная таблица по Entry / Execution / Context / Cost / Permission — большинство команд проигрывают в Context (DerivedData, Keychain, inode), а не в CPU.
- Холодный archive и тёплый кэш могут отличаться в 2–3 раза; на релизной неделе фиксируйте путь DerivedData и запретите ритуальный
flutter clean. - Матрица решений: <5 сборок/день — локально; CI-first — self-hosted bare metal; комплаенс — Xcode Cloud; гибрид — стеки A/B/C.
- Читать далее: pipeline Archive, Flutter Runner, governance swap памяти, codesign — ссылки в конце статьи.
Почему сборки Xcode кажутся медленными — и почему винят не тот слой
При разговоре об оптимизации сборки Xcode всплывают RAM, -jobs и флаги Swift. В реальных iOS/Flutter pipeline разброс xcodebuild archive определяется возможностью переиспользовать контекст выполнения: путь DerivedData, индексированный граф CocoaPods, пересоздание Keychain, конкуренция за inode на релизной неделе.
Если локальные 8 минут становятся 20 на GitHub Actions, Apple Silicon не стал медленнее — CI трактует машину как одноразовую среду: холодный checkout, повторное разрешение pod, импорт сертификата каждый run. На общем Mac VPS добавляются соседи по CPU/IO, вытеснение кэша и ночные бэкапы.
- Миф 1: крутить
-jobs, игнорируя дрейф пути DerivedData. - Миф 2: сравнивать один холодный CI с локалью без медианы warm path.
- Миф 3: низкая месячная цена VPS = полная стоимость cloud Mac.
- Миф 4: масштабирование на релизе без фиксации Xcode/Ruby/CocoaPods.
- Миф 5: симулятор + archive на 16 ГБ — swap удваивает время.
Тезис: аренда bare metal продуктизирует переиспользуемый локальный контекст — выделенный M4, квота диска, SSH runner, тёплый DerivedData, при необходимости второй узел — медиана warm archive около трети от холодного CI (≈3x), не магические флаги.
Три столпа ускорения bare metal 3x
3x — та же ветка, тот же Podfile.lock, Release archive, медиана warm path против базы «холодный GitHub Actions + общий VPS». Столпы взаимозависимы.
Столп 1: выделенный bare metal M4 (не общий VPS)
Физическая эксклюзивность: CPU, полоса памяти и запись на SSD без соседей. В контракте: arm64, фиксированный DEVELOPER_DIR, долгосрочный mount DerivedData.
Столп 2: тёплый DerivedData + фиксированные пути
DerivedData — не опциональный кэш. Случайные temp или clean: 8 мин warm → 18 мин cold. Создайте /Users/ci/DerivedData/AppName, укажите -derivedDataPath, запретите лишний flutter clean на релизе.
Столп 3: параллельные узлы (сборка vs тест)
Второй узел — для параллелизма: archive/codesign отдельно, XCTest/симулятор отдельно. Избегает swap на 16 ГБ. >15 сборок/день: второй M4 по дням часто выгоднее 24 ГБ на одной машине.
Пять поверхностей: Entry / Execution / Context / Cost / Permission
Для закупки и архитектурного ревью. Entry — порог входа; Execution — предсказуемость archive; Context — персистентность DerivedData/Keychain/диска; Cost; Permission — сертификаты и сеть.
| Поверхность | Entry | Execution | Context | Cost | Permission |
|---|---|---|---|---|---|
| Локальный MacBook | Нулевой порог при своём железе | Быстрый warm; поездки/выключение | DerivedData на месте; личный Keychain | Sunk cost; сложно параллелить | Свобода сертификатов; слабый аудит |
| GitHub Actions hosted | Старт по YAML | Холодная вариативность; очередь | Эфемерный контекст; cache miss | Поминутная оплата | Bootstrap подписи каждый run |
| Общий Mac VPS | Низкая месячная цена | Непредсказуемо; соседи | DerivedData часто стирают | Дёшево на бумаге; скрытое ожидание | SSH есть; эксклюзивность не гарантирована |
| Аренда bare metal (M4 выделенный) | Дневной PoC + SSH | Стабильный warm archive; scale-out | DerivedData/Keychain долго живут | Гибко день/неделя; ROI на релизе | Self-hosted runner; контроль сертификатов |
| Xcode Cloud | Интеграция Apple | Стандартные pipeline; мало кастома | Кэши Apple | Оплата compute | Глубокая интеграция ASC |
Большинство «CI в 3 раза медленнее» — ноль в Context, а не нехватка CPU.
Cold vs warm: бенчмарки archive (пример)
Пример: среднее Flutter iOS (~180 файлов Swift/ObjC, 12 локальных Pod), Xcode 16.x, Release archive + IPA. Минуты — медиана десяти прогонов.
| Среда | Cold archive | Warm DerivedData archive | Заметки |
|---|---|---|---|
| Локальный M4 MacBook Pro 16 ГБ | 11.2 | 7.4 | Личный Keychain |
| GitHub Actions macos-14 | 21.8 | 14.6 | Кэш чувствителен к path/key |
| Общий Mac VPS (4 vCPU на витрине) | 19.5 | 13.1 | +30% при IO соседа |
| Выделенный M4 bare metal (один узел) | 12.0 | 6.8 | Фиксированный derivedDataPath |
| Bare metal dual node | — | 6.8 wall time (параллельно) | Archive и XCTest на разных хостах |
Против 21,8 мин холодного hosting 6,8 мин warm bare metal ≈ 3,2x. Ежедневный clean DerivedData возвращает к cold — оптимизируется контекст.
Матрица: нужен ли bare metal runner?
Пять профилей — при двух и более «bare metal first» недельный дневной PoC с медианой warm.
| Профиль | Сборок/день | Сложность подписи | Рекомендация | Почему |
|---|---|---|---|---|
| Соло-разработчик | <3/день | Один сертификат | Локальный MacBook | Контекст уже локально |
| Малая Flutter-команда | 5–15/день | Multi-flavor + Pod | Bare metal + self-hosted runner | Warm DerivedData даёт максимум |
| Аутсорс, параллельные проекты | Пик 30+/день | Несколько Team ID | Dual node bare metal | Разделить build/test |
| Регулируемый финтех/мед | Средне | HSM/аудит | Bare metal + изоляция сети | Нужен контроль Permission |
| Чистая Apple-команда | Низко | ASC интеграция | Xcode Cloud или локально | Достаточно при малом кастоме |
Рекомендуемые стеки A / B / C
Три уровня зрелости — неделя пилота A, затем B/C.
Стек A: hosted CI + гигиена путей
Остаться на GitHub Actions с -derivedDataPath, pod install --deployment, раздельными job подписи, concurrency. <5 сборок/день. 20–35% экономии, редко 3x.
Стек B: один self-hosted bare metal runner
Аренда M4, launchd runner, DerivedData и Pods на одном SSD, триггер GitHub/GitLab. Статьи Flutter Runner и codesign для Keychain. Стабильный релизный ритм.
Стек C: dual node + archive-специалист (релизная неделя)
Основной узел warm archive; вторичный — тесты и нотаризация. Третий дневной узел для hotfix. Статьи Archive и swap памяти.
Ловушки, которые bare metal не исправит
flutter cleanили удаление DerivedData каждый CI.- Незакоммиченный
Podfile.lock. brew upgradeXcode на runner в релизную неделю.- 16 ГБ: симулятор + archive — swap x2.
- Неидемпотентный импорт сертификатов.
- Сравнение одного лучшего run вместо медианы десяти.
Чеклист из 7 шагов
- Базовая линия: десять archive с таймингом этапов.
- Фиксировать пути: постоянные
DerivedDataиPods. - Дневной PoC: M4, десять одинаковых workflow, медиана warm.
- Подпись: CI-пользователь и Keychain по статье codesign.
- Параллель: второй узел или сдвиг cron.
- Governance: алерты disk/inode и swap.
- Закупка: ROI, затем неделя/месяц.
FAQ
Хватит ли флагов компилятора для 3x?
Вряд ли. Инкрементальная компиляция Swift, кэш модулей и линковка доминируют.
Чем bare metal отличается от cloud Mac?
Cloud Mac — категория. Подтвердите физическую эксклюзивность, arm64, квоту диска, долгий DerivedData.
Разве cache GitHub Actions не хватит?
Помогает, но не заменяет горячее состояние SSD. Self-hosted bare metal ближе к локали.
Xcode Cloud проще?
Если ASC-центрично и мало кастома — да. Иначе гибче self-hosted bare metal.
Хватит 16 ГБ?
Для одного archive часто да; параллельные симуляторы — 24 ГБ или dual node.
Когда виден ROI?
>8 сборок/день и +12 мин ожидания — часто ясно за неделю PoC.
Итог
Правильная история оптимизации Xcode — сохранять контекст выполнения. Выделенный M4 bare metal + тёплый DerivedData + второй узел ≈ 3x warm archive против холодного CI.
- Сначала колонка Context, потом покупка CPU.
- Закупка по медиане warm из десяти прогонов.
- Стек B для большинства; C на релизе.
Дальше: недельный PoC по 7 шагам и связка статей кластера Archive, Flutter, память, подпись.
Нужны предварительно прогреваемые M4 bare metal runner'ы?
kvmboot — выделенный Apple Silicon bare metal по дням/неделям/месяцам для Xcode archive и Flutter iOS CI.