Site icon AppTractor

Анализ производительности для ускорения сборок в Xcode

Производительность сборки может быть проанализирована в самом Xcode. Такой анализ позволит понять узкие места, внести изменения и ускорить рабочий процесс, сэкономив много времени в течение дня для всех разработчиков, работающих над проектом. Медленная сборка часто отвлекает нас, поскольку возникают такие  отвлекающие факторы, как социальные сети и Slack.

Изучив производительность сборки и потратив некоторое время на ее улучшение там, где это возможно, вы увидите, что можно добиться прогресса с помощью нескольких небольших шагов. С помощью нескольких оптимизаций я ускорил время инкрементальной сборки WeTransfer в 4 раза. Давайте рассмотрим имеющиеся на сегодняшний день возможности.

Измерение скорости сборки с помощью Xcode Build Timeline

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

Начните с запуска чистой сборки, используя CMD ⌘ + SHIFT ⇧ + K или выбрав в меню пункт Product ➔ Clean Build Folder…. Папка сборки содержит кэшированные файлы сборки, которые ускоряют инкрементные сборки, но это приводит к получению некорректного начального значения для оптимизации производительности. Очень важно сравнивать сборки, используя ту же среду, которую вы создадите, очистив папку сборки.

Вы можете начать новую сборку после того, как Xcode завершит очистку. После завершения сборки выберите свою сборку в Report Navigator и обязательно откройте помощник, чтобы посмотреть временную шкалу сборки:

Xcode Build Timeline предоставляет подробную информацию о производительности сборки

Ассистент показывает, на что было потрачено все время во время чистой сборки. В приведенном выше примере вы видите оптимизированную версию проекта WeTransfer с оптимизированным распараллеливанием. Последнее означает, что ядра моего MacBook используются оптимально, поскольку каждая строка постоянно заполнена процессами. Я рекомендую вам посмотреть сессию WWDC 2022 “Демистификация распараллеливания в сборках Xcode”, чтобы узнать все об этой концепции.

Мы будем использовать эту временную шкалу сборки в качестве отправной точки для исследования производительности. Вы можете найти потенциальные улучшения, увеличив масштаб определенных блоков, которые выполняются последовательно. Например, на приведенной выше временной шкале показан следующий изолированно выполняемый скрипт:

На временной шкале сборки показан сценарий запуска, который мы можем потенциально оптимизировать

После клика на конкретном блоке можно получить дополнительную информацию о выполняемом процессе. В моем случае я выяснил, какой скрипт мы рассматриваем, и пришел к выводу, что дальнейшая оптимизация для чистых сборок невозможна. Более подробно об этих конкретных оптимизациях я расскажу позже.

Анализ производительности с помощью Build Timing Summary

Build Timing Summary — это еще один базовый показатель, который можно использовать при оптимизации производительности сборки. Он собирает проект один раз и суммируется время, затраченное на каждую категорию.

Действие может быть вызвано в меню продукта с помощью команды Product ➔ Perform Action ➔ Build with Timing Summary или с помощью xcodebuild -showBuildTimingSummary.

Проанализируйте производительность сборки с помощью действия Build Timing Summary

После выполнения этого действия вы увидите, что Xcode начинает сборку вашего проекта на выбранном целевом устройстве или симуляторе.

После завершения сборки перейдите в навигатор отчетов и выберите последнюю сборку. Выберите «Recent» и прокрутите страницу вниз, пока не увидите Build Timing Summary.

Сводка времени сборки показывает, какие этапы сборки занимают больше всего времени

Это может стать еще одной отличной отправной точкой для изучения того, где следует улучшить проект. В большинстве случаев больше всего времени уходит на компиляцию файлов Swift, для чего следует сосредоточиться на оптимизациях распараллеливания. Также я рекомендую посмотреть на время выполнения скриптов, чтобы понять, есть ли возможности для улучшения.

Обратите внимание, что здесь я выполнил чистую сборку, что отличается от инкрементной сборки. Стоит выполнить действие напрямую еще раз, что приведет к получению другого отчета Build Timing Summary. Большую часть времени вы будете выполнять инкрементные сборки, поэтому стоит найти медленные участки, которые влияют как на чистые, так и на инкрементные сборки.

Сводка времени для инкрементальных сборок показывает гораздо более короткий список

Приведенный выше пример представляет собой инкрементную сборку после завершения оптимизации приложения WeTransfer. Мы почти не запускаем никаких скриптов и завершаем сборку за приемлемые 5 секунд.

Как может длительность компиляции исходных текстов Swift быть больше, чем общее время сборки?

Прежде чем мы перейдем к оптимизации, я хочу обратить внимание на то, что компиляция исходных текстов Swift занимает больше времени, чем общее время сборки. Приведенная выше сборка заняла 71 секунду, в то время как компиляция исходных текстов Swift заняла 352 секунды.

Я обратился за разъяснениями к Рику Балларду из команды Xcode Build System, и он дал несколько замечательных пояснений о том, как работает система:

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

Оптимизация фаз сборки

Оптимизация фаз сборки — отличный способ ускорить сборку в Xcode. Некоторые из наших сценариев запуска могут быть не нужны для отладочных сборок и должны быть настроены только для сборки релизов.

Во время написания этой статьи я пытался улучшить время сборки приложения Collect by WeTransfer, которое я создал во время своей работы. Я обнаружил, что большую часть времени мы тратим на выполнение SwiftLint. Для нашей основной цели его выполнение занимало 10 секунд при каждом инкрементальном билде.

Небольшое улучшение мы нашли в добавлении параметра —quiet, но это дало нам лишь менее секунды на сборку. Хоть немного помогло, поэтому мы решили оставить этот параметр. Реальным значительным улучшением стала фильтрация файлов, которые не были изменены. Поскольку мы запускаем SwiftLint во многих наших подмодулях, мы легко получили ~15 секунд улучшения на сборку, включая все цели.

Код, связанный с SwiftLint, довольно специфичен для проектов, использующих этот инструмент. Если вы хотите увидеть наше окончательное решение, я рекомендую вам ознакомиться с этим запросом.

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

Если у вас есть сценарий, который вы хотите запускать только для отладки или релиза, вы можете включить проверку конфигурации:

Оптимизация фаз сборки за счет запуска только для отладочных сборок

В данном случае мы запускаем скрипт SwiftLint только для отладочных сборок. Аналогичным образом можно поступить, установив флажок для сборок «Release», если требуется запускать сценарий только для релизных сборок.

Проверка типов функций и выражений

Чтобы сузить круг причин медленной сборки, можно включить swift-флаги для получения дополнительной информации. Эти флаги были доступны еще в Xcode 10, но они по-прежнему очень полезны.

Компилятор может предупредить об отдельных выражениях, проверка типа которых занимает много времени, с помощью двух frontend флагов:

Значение <limit> может быть заменено на количество миллисекунд, в течение которых выражение должно пройти проверку типа, чтобы было выдано предупреждение.

Чтобы включить эти предупреждения, перейдите в Build Settings ➔ Swift Compiler — Custom Flags ➔ Other Swift Flags:

Флаги Swift для анализа времени компиляции кода

При использовании этой настройки Xcode будет выдавать предупреждение для любой функции, проверка типа которой заняла более 100 мс. Это может указать на методы, которые замедляют время сборки. Разделение таких методов, а также добавление явных типов может привести к повышению производительности билда.

Пример метода, предупреждающего о медленной проверке типа

Приведенный выше метод приводит к медленной проверке типов, что плохо сказывается на производительности всей сборки. В данном случае причиной медленной проверки типа является неправильная работа с перечислением. Добавив NSFetchedResultsChangeType перед .delete и .insert, мы устранили предупреждение:

Исправление медленной проверки типа путем явного указания типа

Настройки сборки для повышения производительности

Ускорение сборки Xcode за счет изменения нескольких настроек было распространенным приемом, позволяющим быстро набрать несколько секунд на инкрементной сборке. В настоящее время в Xcode большинство этих настроек установлены по умолчанию, поэтому их практически не нужно изменять. Однако может оказаться, что вы поддерживаете старый проект, в котором эти настройки все еще необходимо установить или они перезаписаны неверными значениями. Поэтому здесь приведен краткий обзор рекомендуемых на сегодняшний день настроек.

COMPILATION MODE

OPTIMIZATION LEVEL

BUILD ACTIVE ARCHITECTURE ONLY

DEBUG INFORMATION FORMAT (DWARF)

Включение Eager Linking

Вы можете включить ускоренное связывание для своего проекта, чтобы проверить, не приведет ли это к улучшению времени сборки. Если эта функция включена, система сборки будет генерировать так называемый TBD-файл для фреймворков и динамических библиотек, предназначенных только для Swift, чтобы разблокировать связывание зависимых целей до того, как их зависимость закончит связывание. Подробнее о линковке можно узнать, посмотрев сессию WWDC 2022 “Линкуйте быстро: Улучшение времени сборки и запуска”.

Параллельный запуск фаз сценария сборки

Параллельный запуск фаз сценария сборки потенциально может привести к улучшению времени сборки. Я рекомендую сочетать его с User Script Sandboxing для запрета необъявленных input/output зависимостей. Только сценарии с указанными входами и выходами, а также сценарии, настроенные на выполнение на основе анализа зависимостей, будут пытаться выполняться параллельно.

Плагины Swift Package Build

Плагины сборки Swift-пакетов оказывают значительное влияние на время. В приложении WeTransfer я обнаружил, что плагин предварительной сборки приводит к аннулированию кэша и увеличению времени при инкрементных сборках. У нас была возможность перейти на обычный плагин сборки, что привело к уменьшению количества недействительных записей в кэше и ускорению сборки инкрементальных сборок.

Важно также знать, что запуск плагина сборки пакетов всегда занимает около секунды:

Swift Package Build Plugins влияют на время сборки

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

Заключение

Время от времени полезно пересматривать время сборки в Xcode. Вы можете извлечь пользу из каждой выигранной секунды, и помните, что потерянное время накапливается: секунда на каждую сборку — это минута на каждые 60 сборок. Улучшения могут быть достигнуты за счет настроек проекта, фаз сборок и проверки типов в коде.

Спасибо!

Источник

Exit mobile version