[Перевод] Оптимизация времени сборки — Часть 1

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

В этой статье мы узнаем, как профилировать сборку в Xcode и получать метрики. В следующей статье я расскажу о методах устранения узких мест и ускорения сборки проекта. Следует также упомянуть, что мы будем использовать проект Kickstarter iOS, который можно найти на Github. Так что давайте начнем!

Что мы измеряем?


Первое, что мы должны сделать, — определить, что мы пытаемся измерить и оптимизировать. Можно рассмотреть два варианта:

  • Clean build — clean и сборки проекта. Часто clean сборка выполняется на CI для проверки нового pull request и выполнения юнит тестов.
  • Incremental build — сборки проекта после значительных правок исходного кода. Эта сборка создается разработчиком во время работы над новой функциональностью.


В большинстве случаев уменьшение времени при Clean сборке должно также ускорить Incremental сборку. Оптимальным вариантом было бы создание метрик для обоих типов сборок и отслеживание их. Мы будем измерять время сборки, используя Debug конфигурацию только потому, что она используются большую часть времени и оказывают большее влияние на разработку.

Рассчитываем время сборки


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

Отчет об времени сборки в Xcode


Мы можем легко получить данные по времени сборки с помощью Xcode. Он отслеживает все билды по умолчанию и позволяет просматривать время сборки проекта в навигаторе отчетов.

image

Вы также имеете возможность отобразить аналогичную информацию в Xcode на панели activity viewer. Ее можно включить для отображения с помощью командной строки выполнив команду:

defaults write com.apple.dt.Xcode ShowBuildOperationDuration YES

Время сборки проекта отобразиться после сборки вместе с сообщением «Succeeded».

image

Это всего лишь два основных варианта, которые должны дать вам приблизительное представление о Clean и Incremental времени сборки.

Сводка о времени сборки проекта в Xcode


Сводка по времени сборки Xcode — ваш первый друг в получении статистических данных о времени сборки и поиске узких мест. Можно запустить его через Product→Perform Action→Build With Timing Summary. Теперь вы увидите большую разбивку по времени, затраченного на различные задачи:

image

Этот метод должен стать хорошей отправной точкой для поиска наиболее трудоемких задач в процессе сборки. На скриншоте выше видно, что CompileStoryboard, CompileXIB, CompileSwiftSources и фазы PhaseScriptExecution заняли большую часть времени сборки. Xcode удалось выполнить часть задач параллельно, поэтому сборка завершается гораздо быстрее необходимого времени для выполнения задачи каждой из команд.

Мы можем получить сводку по времени сборки для Clean сборки с помощью xcodebuild с опцией — buildWithTimingSummary:

xcodebuild -project 'Kickstarter.xcodeproj' \
-scheme 'Kickstarter-iOS' \
-configuration 'Debug' \
-sdk 'iphonesimulator' \
-showBuildTimingSummary \
clean build | sed -n -e '/Build Timing Summary/,$p'

Build Timing Summary
CompileStoryboard (29 tasks) | 87.128 seconds
CompileSwiftSources (4 tasks) | 54.144 seconds
PhaseScriptExecution (14 tasks) | 18.167 seconds
CompileAssetCatalog (2 tasks) | 6.532 seconds
CompileXIB (21 tasks) | 6.293 seconds
CodeSign (7 tasks) | 3.069 seconds
Ld (4 tasks) | 2.342 seconds
LinkStoryboards (2 tasks) | 0.172 seconds
CompileC (3 tasks) | 0.122 seconds
Ditto (20 tasks) | 0.076 seconds
Touch (4 tasks) | 0.007 seconds
** BUILD SUCCEEDED ** [92.620 sec]

Теперь давайте получим аналогичные метрики для Incremental сборки. Следует отметить, что время Incremental сборки полностью зависит от файлов, изменяемых в проекте. Для получения согласованных результатов можно изменить один файл и скомпилировать проект. В отличие от Buck или Bazel систем, Xcode использует метки времени для определения того, что изменилось и что нужно пересобрать. Мы можем обновить timestamp, используя touch:

touch KsApi/mutations/CancelBackingMutation.swift && \
xcodebuild -project 'Kickstarter.xcodeproj' \
-scheme 'Kickstarter-iOS' \
-configuration 'Debug' \
-sdk 'iphonesimulator' \
-showBuildTimingSummary \
build | sed -n -e '/Build Timing Summary/,$p'

Build Timing Summary
PhaseScriptExecution (14 tasks) | 18.089 seconds
CodeSign (7 tasks) | 2.990 seconds
CompileSwiftSources (1 task) | 1.245 seconds
Ld (1 task) | 0.361 seconds
** BUILD SUCCEEDED ** [23.927 sec]

Предупреждения при проверке типов


Когда время компиляции является узким местом, мы можем получить дополнительную информацию, установив Other Swift Flags в параметры сборки проекта в Xcode. При включенном флаге Xcode генерирует предупреждение для любой функции или выражение, которое занимает более 100ms для определения типа переменных:

  • -Xfrontend -warn-long-function-bodies=100
  • -Xfrontend -warn-long-expression-type-checking=100

image

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

Опции диагностики компилятора


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

  • -driver-time-compilation — высокий уровень синхронизации заданий, выполняемых драйвером.
  • -Xfrontend -debug-time-compilation — добавляет таймеры для каждого этапа выполнения фронтенда.
  • -Xfrontend -debug-time-function-bodies — подсчёт времени на ввод каждой функции в приложение.
  • -Xfrontend -debug-time-expression-type-checking — подсчёт времени затраченного на ввод каждого выражения в программе.

Давайте использовать флажок -debug-time-compilation, чтобы получить информацию о самых медленных файлах при компиляции:

xcodebuild -project 'Kickstarter.xcodeproj' \
-scheme 'Kickstarter-iOS' \
-configuration 'Debug' \
-sdk 'iphonesimulator' \
clean build \
OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-compilation" |
awk '/CompileSwift normal/,/Swift compilation/{print; getline; print; getline; print}' |
grep -Eo "^CompileSwift.+\.swift|\d+\.\d+ seconds" |
sed -e 'N;s/\(.*\)\n\(.*\)/\2 \1/' |
sed -e "s|CompileSwift normal x86_64 $(pwd)/||" |
sort -rn |
head -3

25.6026 seconds Library/ViewModels/SettingsNewslettersCellViewModel.swift
24.4429 seconds Library/ViewModels/PledgeSummaryViewModel.swift
24.4312 seconds Library/ViewModels/PaymentMethodsViewModel.swift

Как вы видите, для компиляции SettingsNewslettersCellViewModel.swift потребовалось 25 секунд. Из журнала сборки можно получить дополнительные сведения о времени компиляции файла:

===-------------------------------------------------------------------------===
Swift compilation
===-------------------------------------------------------------------------===
Total Execution Time: 25.6026 seconds (26.6593 wall clock)

---User Time--- --System Time-- --User+System-- ---Wall Time--- --- Name ---
24.4632 (98.3%) 0.5406 (76.5%) 25.0037 (97.7%) 26.0001 (97.5%) Type checking and Semantic analysis
0.0981 (0.4%) 0.1383 (19.6%) 0.2364 (0.9%) 0.2872 (1.1%) Name binding
0.1788 (0.7%) 0.0043 (0.6%) 0.1831 (0.7%) 0.1839 (0.7%) IRGen
0.0508 (0.2%) 0.0049 (0.7%) 0.0557 (0.2%) 0.0641 (0.2%) Parsing
0.0599 (0.2%) 0.0020 (0.3%) 0.0619 (0.2%) 0.0620 (0.2%) SILGen
0.0285 (0.1%) 0.0148 (2.1%) 0.0433 (0.2%) 0.0435 (0.2%) SIL optimization
0.0146 (0.1%) 0.0015 (0.2%) 0.0161 (0.1%) 0.0162 (0.1%) Serialization, swiftmodule
0.0016 (0.0%) 0.0006 (0.1%) 0.0022 (0.0%) 0.0022 (0.0%) Serialization, swiftdoc
0.0000 (0.0%) 0.0000 (0.0%) 0.0000 (0.0%) 0.0001 (0.0%) SIL verification, pre-optimization
0.0000 (0.0%) 0.0000 (0.0%) 0.0000 (0.0%) 0.0000 (0.0%) AST verification
0.0000 (0.0%) 0.0000 (0.0%) 0.0000 (0.0%) 0.0000 (0.0%) SIL verification, post-optimization
24.8956 (100.0%) 0.7069 (100.0%) 25.6026 (100.0%) 26.6593 (100.0%) Total

Теперь очевидно, что Type checking и Semantic analysis — это наиболее трудоемкая работа. Давайте пойдём дальше и перечислим самые медленные функций и выражения на этапе Type Checking:

xcodebuild -project 'Kickstarter.xcodeproj' \
-scheme 'Kickstarter-iOS' \
-configuration 'Debug' \
-sdk 'iphonesimulator' \
clean build \
OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-expression-type-checking \
-Xfrontend -debug-time-function-bodies" |
grep -o "^\d*.\d*ms\t[^$]*$" |
awk '!visited[$0]++' |
sed -e "s|$(pwd)/||" |
sort -rn |
head -5

16226.04ms Library/Styles/UpdateDraftStyles.swift:31:3
10551.24ms Kickstarter-iOS/Views/RewardCardContainerView.swift:171:16 instance method configureBaseGradientView ()
10547.41ms Kickstarter-iOS/Views/RewardCardContainerView.swift:172:7
8639.30ms Kickstarter-iOS/Views/Controllers/AddNewCardViewController.swift:396:67
8233.27ms KsApi/models/templates/ProjectTemplates.swift:94:5

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

Время сборки таргета


Целесообразно измерять время сборки таргет отдельно и отображать их на графике. Можно помочь понять, какие таргеты собираються или могут собираться параллельно. Для этого можно использовать инструмент xcode-build-time-rendering. Давайте установим его в качестве RubyGem:

gem install xcode-build-times

После завершения установки выполните следующую команду для ввода журнала меток времени в Run Script Build Phase ваших целевых объектов:

xcode-build-times install

Затем скомпилируйте проект и отчет с помощью:

xcode-build-times generate

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

image

Агрегированные метрики


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

git clone https://github.com/spotify/XCLogParser
rake build
xclogparser parse --project Kickstarter --reporter html

Это отчет, созданный для проекта Kickstarter iOS:

image

Автоматизация


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

Заключение


Снижение времени сборки проекта имеет решающее значение для повышения производительности разработчиков. Сегодня мы научились измерять время сборки проекта и получать некоторые метрики для анализа…

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

Спасибо за прочтение!

© Habrahabr.ru