Инженерное руководство

Диагностика узких мест компиляции Swift на облачном Mac

Диагностика узких мест компиляции Swift на облачном Mac

Когда компиляция модуля 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 и стабильно ли улучшилось общее время сборки в нескольких запусках. Если изменилось только общее время, а горячий участок остался прежним, продолжайте проверять кэш и фоновые задачи, а не приписывайте результат изменению исходного кода.

Превратите диагностику в сопровождаемый процесс приёмки

Диагностические параметры не следует постоянно включать во все сборки. Надёжнее создать отдельную задачу проверки производительности, запускать её по мере необходимости и архивировать сведения об инструментах, коммите, команде и сводке. Журналы могут содержать локальные пути, структуру репозитория или раскрытые значения переменных окружения, поэтому перед загрузкой их необходимо обезличить.

Для каждого исследования рекомендуется сохранять:

  1. Версии Xcode и Swift.
  2. Коммит Git и конфигурацию сборки.
  3. Явную отметку о чистой или инкрементальной сборке.
  4. Исходные результаты времени как минимум трёх запусков.
  5. Сводку этапов и основные горячие участки исходного кода.
  6. Единственное различие между состояниями до и после изменения, а также способ отката.

Если результаты на облачном Mac заметно колеблются, сначала проверьте, не выполняются ли параллельные сборки, индексирование или оставшиеся запущенными скрипты, а затем повторите измерения. MiniRent предоставляет выделенные физические узлы, однако задачи, запущенные пользователем на одном устройстве, всё равно конкурируют за процессор, память и диск. Надёжные выводы основаны на контролируемых входных данных, воспроизводимых командах и полном наборе сохранённых свидетельств, а не на одной сборке, которая показалась быстрее.

Часто задаваемые вопросы

Почему нельзя сравнивать только полное время двух сборок Xcode?

В него входят разрешение зависимостей, состояние кешей, скрипты, компиляция и компоновка. Сначала зафиксируйте коммит, версию Xcode и параметры, затем сравнивайте этапы.

Стоит ли постоянно держать включённой диагностику времени Swift?

Нет. Она создаёт большой объём журналов и может меняться вместе с инструментами. Включайте параметры для контролируемого замера, сохраняйте результаты и затем удаляйте их.

Облачный Mac на выделенном физическом узле

Запускайте сборки на выделенном физическом узле

Выбирайте MiniRents M4 или MiniRents M4 Pro с оплатой за день, неделю, месяц или квартал, а узел — с учётом команды и расположения репозитория. Устройства работают на выделенных физических машинах, а не в виртуальной среде.

Выбрать модель и заказать