Медленные сборки часто отвлекают нас, позволяя сосредоточиться на таких отвлекающих факторах, как социальные сети и Slack. Но производительность сборки в Xcode можно проанализировать и, соответственно, ускорить. Это может быстро улучшить рабочий процесс и сэкономить много времени всем разработчикам, работающим над проектом.
Изучив производительность сборки и потратив время на её улучшение там, где это возможно, вы увидите, что можете добиться прогресса несколькими небольшими шагами. Я улучшил время инкрементальной сборки RocketSim в 4 раза, используя несколько оптимизаций. Давайте рассмотрим доступные нам на сегодняшний день возможности (предыдущая статья 2023 года).
Измерение сборки с помощью Xcode Build Timeline
Прежде чем мы начнём углубляться в оптимизацию, важно измерить базовый уровень для вашего проекта. Вы можете использовать его как основу по мере внедрения улучшений.
Начните с запуска чистой сборки, используя сочетание клавиш CMD ⌘ + SHIFT ⇧ + K, или выберите в меню пункт Product ➔ Clean Build Folder…. Папка сборки содержит кэшированные файлы сборки, которые ускоряют инкрементальные сборки, но создают некорректный базовый уровень для оптимизации производительности. Важно сравнивать сборки, используя ту же среду, которую вы создадите, очистив папку сборки.
Вы можете начать новую сборку после того, как Xcode завершит очистку, используя сочетание клавиш CMD ⌘ + B. Выберите сборку в Report Navigator после её завершения и обязательно откройте помощник, чтобы отобразить Build Timeline:
Вы можете проанализировать производительность сборки с помощью помощника Build Navigator в Xcode.
На изображении выше показаны точные шаги для выполнения:
- Откройте навигатор сборки
- Выберите последнюю чистую сборку
- Запишите текущую длительность сборки — это будет ваша базовая отметка
- Откройте помощник
- Начните изучать статистику
Помощник покажет, на что тратится время во время чистой сборки. В примере выше вы видите оптимизированную версию проекта RocketSim с оптимизированным распараллеливанием. Последнее означает, что ядра моего MacBook используются оптимально, поскольку каждая строка постоянно заполнена процессами сборки. Рекомендую вам посмотреть сессию WWDC 2022 «Развенчание мифов о распараллеливании в сборках Xcode», чтобы узнать больше об этой концепции.
Мы будем использовать эту временную шкалу в качестве отправной точки для исследования производительности. Вы можете найти потенциальные улучшения, увеличивая масштаб отдельных блоков, которые выполняются последовательно.
Анализ инкрементальных сборок
Я решил запустить ещё одну сборку, которая станет инкрементальной. Взглянув на помощника, мы видим, что он стал гораздо менее загруженным. Это связано с тем, что он собирает проект инкрементально. Тем не менее, на один элемент тратится много времени:
Помощник по навигатору сборки показывает, что инкрементальная сборка тратит много времени на выполнение скрипта сборки.
Нажав на конкретный блок, вы можете получить дополнительную информацию о выполненном процессе. В моём случае я обнаружил, что все мои отладочные сборки загружали DSYM-файлы в Sentry. Конечно, это не требуется для каждой отладочной сборки, поэтому я сэкономил 6.3 секунды с каждой инкрементальной сборки!
В данном случае я добился этого, добавив оператор if вокруг скрипта, чтобы он запускался только для релизных сборок:
if [ "${CONFIGURATION}" == "Release" ]; then
# ... Build script logic
else
echo "Skipping build script for non-release builds."
fi
Аналитика производительности сборки с помощью Build Timing Summary
Build Timing Summary (cводка по времени сборки) — это ещё один базовый показатель, который можно использовать при оптимизации производительности. Она собирает ваш проект один раз и суммирует затраченное время по каждой категории.
Действие можно запустить в меню продукта, выбрав Product ➔ Perform Action ➔ Build with Timing Summary или использовать xcodebuild -showBuildTimingSummary.
Проанализируйте производительность сборки с помощью Build Timing Summary.
После выполнения этого действия вы увидите, что Xcode начинает сборку вашего проекта на выбранном целевом устройстве или симуляторе.
После завершения сборки перейдите в навигатор отчётов и выберите последнюю сборку. Выберите Recent и прокрутите страницу вниз до Build Timing Summary. Вот результат для проекта RocketSim с чистой сборкой:
Сводка по времени сборки показывает, на что ваши сборки тратят больше всего времени.
Мне нравится этот обзор как отправная точка, когда я уделяю время оптимизации сборки. Сводка по времени сборки находится слева, а более подробный обзор — справа. В большинстве случаев большая часть времени уходит на компиляцию файлов Swift, для которой следует сосредоточиться на оптимизации распараллеливания. Также рекомендую вам посмотреть на время выполнения скрипта запуска, чтобы понять, есть ли возможности для улучшения.
Обратите внимание, что я выполнил чистую сборку, которая отличается от инкрементальной. Стоит выполнить это действие ещё раз, чтобы получить другую сводку по времени сборки. Вы будете запускать инкрементальные сборки большую часть времени, поэтому стоит найти медленные части, которые влияют как на чистую, так и на инкрементальную сборку.
Сводка по времени для инкрементальной сборки показывает гораздо более короткий список.
Приведённый выше пример представляет инкрементальную сборку после завершения оптимизации проекта RocketSim. Мы запустили почти все скрипты и завершили сборку за приемлемые 3.9 секунды.
Как компиляция исходников Swift может занимать больше времени, чем общая сборка?
Прежде чем мы углубимся в оптимизацию, хочу отметить, что компиляция исходников Swift занимает больше времени, чем общая сборка. Чистая сборка, показанная выше, заняла 37.3 секунды, а компиляция исходников Swift — 281 секунду.
Я обратился к Рику Балларду из команды Xcode Build System за разъяснениями, и он дал несколько полезных советов о том, как работает система:
Да, многие команды, особенно компиляции, могут выполняться параллельно друг с другом, поэтому многоядерные машины завершат сборку гораздо быстрее, чем время, необходимое для выполнения каждой из команд.
Оптимизация фаз сборки
Оптимизация фаз сборки — отличный способ ускорить сборку в Xcode. Некоторые из наших скриптов запуска могут не требоваться для отладочных сборок и должны быть настроены только для релизных сборок.
Раньше я пытался сократить время сборки приложения WeTransfer. Я обнаружил, что большая часть времени тратилась на выполнение SwiftLint. Для нашей основной цели выполнение каждой инкрементальной сборки занимало 10 секунд.
Одним из небольших улучшений, которые мы сделали, стало добавление параметра --quiet, но мы выиграли всего меньше секунды на сборку. Всё к лучшему, поэтому мы решили оставить его. Нашим действительно значительным улучшением стала фильтрация файлов, которые не были изменены. Поскольку мы используем SwiftLint во многих наших подмодулях, мы легко получили около 15 секунд ускорения на сборку, включая все целевые сборки.
Запускайте фазу сборки только при необходимости
Если у вас есть скрипт сборки, который вы хотите запускать только для отладки или релизных сборок, вы можете включить проверку конфигурации:
Оптимизация фаз сборки за счёт запуска только для отладочных сборок.
В данном случае мы запускаем скрипт SwiftLint только для отладочных сборок. Вы, очевидно, можете сделать то же самое, проверив сборки «Release», если хотите запустить скрипт только для релизных сборок.
Проверка типов функций и выражений
Чтобы сузить круг причин медленной сборки, можно включить swift-flags для сбора более подробной информации. Эти флаги были доступны ещё до Xcode 10, но по-прежнему очень полезны.
Компилятор может предупреждать об отдельных выражениях, проверка типов которых занимает много времени, с помощью двух фронтенд флагов:
-Xfrontend -warn-long-function-bodies=<limit>-Xfrontend -warn-long-expression-type-checking=<limit>
Значение <limit> можно заменить на количество миллисекунд, которое должно пройти для проверки типов выражения, чтобы было выдано предупреждение.
Чтобы включить эти предупреждения, перейдите в Build Settings ➔ Swift Compiler — Custom Flags ➔ Other Swift Flags:
Проверка типов в Xcode помогает выявить медленно работающие фрагменты кода.
С этой настройкой Xcode будет выдавать предупреждение для любой функции, проверка типов которой заняла более 100 мс. Это может указать на методы, замедляющие сборку. Разделение этих методов и добавление явных типов может повысить производительность сборки.
Настройки для ускорения сборки
Раньше ускорение сборки в Xcode путём изменения нескольких настроек было распространённым способом быстро выиграть секунды при инкрементальной сборке. В настоящее время в Xcode большинство этих настроек установлены по умолчанию, поэтому здесь мало что можно рассказать. Однако, возможно, вы поддерживаете старый проект, в котором эти настройки всё ещё необходимо настроить, или они перезаписываются неправильными значениями. Поэтому ниже представлен краткий обзор рекомендуемых на сегодняшний день настроек.
Режим компиляции
- Debug: Incremental
- Release: Whole Module
Уровень оптимизации
- Debug: No Optimization [-O0]
- Release: Fastest, Smallest [-Os]
Сборка только для активной архитектуры
- Debug: Yes
- Release: No
Формат отладочной информации (DWARF)
- Debug – Any iOS Simulator SDK: DWARF
- Release – Any iOS SDK : DWARF with DSYM File
Включение жадного связывания
Вы можете включить жадное связывание (eager linking) для своего проекта, чтобы проверить, приведёт ли это к сокращению времени сборки. Если это включено, система сборки создаст так называемый TBD-файл для целевых фреймворков и динамических библиотек, работающих только на Swift, чтобы разблокировать связывание зависимых целевых объектов до того, как их зависимости будут связаны. Вы можете узнать больше о связывании, посмотрев сессию WWDC 2022 «Быстрая сборка: сокращение времени сборки и запуска».
Параллельный запуск фаз скрипта сборки
Параллельный запуск фаз скрипта сборки может потенциально сократить время сборки. Я рекомендую сочетать его с функцией «Песочница пользовательских скриптов» (User Script Sandboxing), чтобы запретить необъявленные зависимости ввода/вывода. Параллельный запуск будет осуществляться только для скриптов с указанными входными и выходными данными, а также для скриптов, настроенных на запуск на основе анализа зависимостей.
Плагины для сборки пакетов Swift
Плагины для сборки пакетов Swift существенно влияют на время сборки. В приложении WeTransfer я обнаружил, что готовый плагин приводит к инвалидации кэша сборки и увеличивает время инкрементальной сборки. У нас была возможность перейти на обычный плагин сборки, что уменьшило количество инвалидаций кэша и ускорило инкрементальные сборки.
Также важно знать, что запуск плагина для сборки пакетов всегда занимает некоторое время:
Плагины сборки пакетов Swift могут влиять на время сборки.
В приведенном выше примере не было файлов для линтинга, а плагин сборки не возвращал команд сборки. Я ожидал практически нулевого влияния на общее время сборки, но ещё предстоит кое-что сделать. Обратите внимание, что эти показатели могут улучшиться с появлением новых версий Xcode. Моя главная цель — повысить осведомлённость.
Автоматический мониторинг времени сборки
Вы, вероятно, читаете эту статью, обнаружив, что ваши сборки стали медленнее. В идеале вы должны получать автоматические уведомления о замедлении сборки, чтобы точно определить причину. Например, я бы получил уведомление о добавлении новой фазы скрипта сборки, поскольку она внезапно увеличила время сборки на 6 секунд.
Для сравнения:
- Сборки на 6 секунд медленнее
- 100 сборок в день
- 10 разработчиков
Это 6 * 100 * 10 = 6000 секунд, то есть на 1 час 40 минут больше времени, потраченного на ожидание завершения сборки в день.
Именно поэтому более 200 команд начали использовать RocketSim для Teams.
Аналитика сборки RocketSim отображает длительность сборки с течением времени.
На панели управления RocketSim вы можете изучить статистику продолжительности сборки для всей вашей команды. Вы можете ответить на такие вопросы, как:
- Какой компьютер работает лучше всего? Или: стоит ли перевести всю команду на последний MacBook?
- Как версия macOS или Xcode влияет на производительность сборки?
- Сокращается ли время сборки?
Последнее особенно важно, поскольку оно даёт вам необходимую информацию для решения о необходимости пересмотра производительности сборки.
Заключение
Время от времени полезно пересматривать время сборки в Xcode. Вы можете использовать каждую сэкономленную секунду, и помните, что они накапливаются со временем: секунда на каждую сборку — это минута на каждые 60 выполненных сборок. Улучшения можно вносить в настройки проекта, этапы сборки и проверки типов кода.
