Когда компиляция модуля Swift постепенно замедляется с нескольких десятков секунд до нескольких минут, не стоит сразу переписывать код или увеличивать параллелизм. В удалённой среде общее время Xcode включает разрешение зависимостей, выполнение скриптов сборки, компоновку и проверку типов. Эффективная диагностика начинается с фиксации инструментов и входных данных на облачном Mac, затем собираются данные по отдельным этапам, после чего проблема локализуется до конкретных исходных файлов и выражений.
Сначала создайте воспроизводимую базу измерений
Выберите период, когда не выполняются другие задачи сборки, и зафиксируйте коммит, Scheme, Configuration, SDK и целевую архитектуру. Сохраните вывод xcodebuild -version, текущий коммит и фактически выполняемую команду. Не сравнивайте напрямую одну чистую сборку с одной инкрементальной: они отвечают на разные вопросы.
mkdir -p "$HOME/build-audit"
xcodebuild -version > "$HOME/build-audit/toolchain.txt"
git rev-parse HEAD > "$HOME/build-audit/commit.txt"
set -o pipefail
/usr/bin/time -l xcodebuild \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-configuration Release \
-destination 'generic/platform=iOS' \
-showBuildTimingSummary \
build \
2>&1 | tee "$HOME/build-audit/baseline.log"
Сначала выполните три обычные инкрементальные сборки подряд и проверьте, стабилен ли разброс времени. Для измерения чистой сборки явно запускайте clean либо используйте отдельный путь DerivedData. До и после оптимизации способ очистки должен быть полностью одинаковым.
Единственный самый быстрый результат не может служить основанием для вывода. Сравнивайте медиану нескольких запусков в одинаковых условиях и сохраняйте журнал самого медленного запуска, чтобы проверить эпизодические скрипты, сетевые запросы и конкуренцию за ресурсы.
Определите направление диагностики по сводке этапов
Build Timing Summary показывает время компиляции, компоновки, обработки ресурсов, выполнения скриптов и других этапов. Сначала найдите наиболее затратные элементы, которые повторяются от запуска к запуску, а не ориентируйтесь только на итоговое время в последней строке.
| Наблюдение | Что проверить в первую очередь | Типичная ошибка |
|---|---|---|
| SwiftCompile стабильно занимает больше всего времени | Проверку типов, размер отдельных файлов, пакетную компиляцию | Ошибочно считать узким местом компоновку |
| Run Script выполняется при каждой сборке | Входные и выходные файлы, область сканирования внутри скрипта | Только увеличивать параллелизм машины |
| Время разрешения зависимостей нестабильно | Файл блокировки, доступ к репозиториям, повторный запуск разрешения | Считать это проблемой компиляции Swift |
| Выделяется этап Link | Входные данные компоновщика, отладочные символы, дублирующиеся библиотеки | Переписывать выражения бизнес-логики |
Также проверьте, объявлены ли у скриптов входные и выходные файлы. Скрипт без заданных границ зависимостей может запускаться при каждой инкрементальной сборке и скрывать эффект от оптимизации исходного кода. Если скрипт что-либо загружает, измеряйте сетевое ожидание отдельно от локальных вычислений.
Найдите медленные функции и выражения
Убедившись, что узкое место находится в компиляции Swift, временно добавьте диагностические параметры фронтенда. Их можно указать в OTHER_SWIFT_FLAGS отдельной диагностической Configuration, чтобы не затрагивать повседневные настройки команды.
-Xfrontend -debug-time-function-bodies
-Xfrontend -debug-time-expression-type-checking
Повторно выполните ту же команду сборки, сохранив также стандартный поток ошибок. Обычно журнал содержит затраченное время, расположение файла и соответствующую функцию или выражение. Формат вывода и пороговые значения могут меняться между версиями инструментов Xcode, поэтому скрипт анализа не должен исходить из того, что количество столбцов всегда остаётся неизменным.
Что оптимизировать в первую очередь
Сначала отсортируйте результаты по времени, а затем сгруппируйте их по файлам. Умеренно затратный участок, компилируемый сотни раз, может оказаться важнее одной исключительно медленной функции. К типичным дорогостоящим конструкциям относятся длинные цепочки обобщений, вложенные замыкания, отдельные выражения с большим количеством ветвей и преобразования коллекций, в которых компилятор должен одновременно выводить несколько промежуточных типов.
За один раз устраняйте только один тип проблемы: например, разбейте выражение, явно укажите типы промежуточных значений или разделите крупную функцию на небольшие функции с чёткими границами. Не изменяйте одновременно настройки компиляции и исходный код, иначе определить источник улучшения будет невозможно.
Проверьте причину минимальным изменением
Предположим, один участок кода объединяет фильтрацию, отображение, создание словаря и обработку опциональных значений в одном выражении. Сначала можно присвоить промежуточным результатам имена и явно указать их типы. Цель состоит не в сокращении исходного кода, а в уменьшении количества ограничений, которые проверка типов должна разрешить за один раз.
let validItems: [Item] = items.filter { $0.isValid }
let identifiers: [String] = validItems.map(\.identifier)
let result: [String: Item] = Dictionary(
uniqueKeysWithValues: zip(identifiers, validItems)
)
После изменения повторите измерения в ветке, которая отличается от базового коммита только этой правкой. Проверьте как минимум три показателя: снизилось ли время выражения в журнале горячих участков, сократился ли этап SwiftCompile и стабильно ли улучшилось общее время сборки в нескольких запусках. Если изменилось только общее время, а горячий участок остался прежним, продолжайте проверять кэш и фоновые задачи, а не приписывайте результат изменению исходного кода.
Превратите диагностику в сопровождаемый процесс приёмки
Диагностические параметры не следует постоянно включать во все сборки. Надёжнее создать отдельную задачу проверки производительности, запускать её по мере необходимости и архивировать сведения об инструментах, коммите, команде и сводке. Журналы могут содержать локальные пути, структуру репозитория или раскрытые значения переменных окружения, поэтому перед загрузкой их необходимо обезличить.
Для каждого исследования рекомендуется сохранять:
- Версии Xcode и Swift.
- Коммит Git и конфигурацию сборки.
- Явную отметку о чистой или инкрементальной сборке.
- Исходные результаты времени как минимум трёх запусков.
- Сводку этапов и основные горячие участки исходного кода.
- Единственное различие между состояниями до и после изменения, а также способ отката.
Если результаты на облачном Mac заметно колеблются, сначала проверьте, не выполняются ли параллельные сборки, индексирование или оставшиеся запущенными скрипты, а затем повторите измерения. MiniRent предоставляет выделенные физические узлы, однако задачи, запущенные пользователем на одном устройстве, всё равно конкурируют за процессор, память и диск. Надёжные выводы основаны на контролируемых входных данных, воспроизводимых командах и полном наборе сохранённых свидетельств, а не на одной сборке, которая показалась быстрее.
Часто задаваемые вопросы
Почему нельзя сравнивать только полное время двух сборок Xcode?
В него входят разрешение зависимостей, состояние кешей, скрипты, компиляция и компоновка. Сначала зафиксируйте коммит, версию Xcode и параметры, затем сравнивайте этапы.
Стоит ли постоянно держать включённой диагностику времени Swift?
Нет. Она создаёт большой объём журналов и может меняться вместе с инструментами. Включайте параметры для контролируемого замера, сохраняйте результаты и затем удаляйте их.
Облачный Mac на выделенном физическом узле
Запускайте сборки на выделенном физическом узле
Выбирайте MiniRents M4 или MiniRents M4 Pro с оплатой за день, неделю, месяц или квартал, а узел — с учётом команды и расположения репозитория. Устройства работают на выделенных физических машинах, а не в виртуальной среде.