Build Time Optimization - Part 1

Almost every developer at least once encountered a rather long build time for their project. This leads to reduced productivity and slows down the development process of the entire team. As you can see, increasing the build time of a project is crucial because it has a direct impact on the time the application is published to the AppStore and for the faster release of new features in your application.

In this article, we will learn how to profile an assembly in Xcode and get metrics. In the next article I will talk about methods to eliminate bottlenecks and speed up the assembly of the project. It should also be mentioned that we will use the Kickstarter iOS project, which can be found on Github . So let's get started!

What are we measuring?


The first thing we must do is to determine what we are trying to measure and optimize. Two options can be considered:

  • Clean build - clean and build the project. Often, clean builds are done in CI to check for a new pull request and run unit tests.
  • Incremental build - build the project after significant changes to the source code. This assembly is created by the developer while working on new functionality.

In most cases, reducing the time during Clean build should also speed up the Incremental build. The best option would be to create metrics for both types of assemblies and track them. We will measure build time using the Debug configuration only because it is used most of the time and has a greater impact on development.

We calculate the assembly time


The most effective way to improve build time should be an approach based on implementing and verifying changes based on specific metrics. Consider them, as well as tools that we can use to obtain information about the build time of the project.

Xcode build time report


We can easily get build time data using Xcode. It tracks all builds by default and allows you to view the build time of the project in the report navigator.

image

You also have the option of displaying similar information in Xcode in the activity viewer panel . It can be enabled for display using the command line by running the command: The project build time is displayed after the assembly along with the message “Succeeded”. These are just two basic options that should give you a rough idea of Clean and Incremental build time.

defaults write com.apple.dt.Xcode ShowBuildOperationDuration YES



image



Project build time summary in Xcode


Xcode Build Time Summary - Your first friend to get build time statistics and bottlenecks. You can run it through Product-> Perform Action-> Build With Timing Summary . Now you will see a large breakdown of the time spent on various tasks:

image

This method should be a good starting point for finding the most time-consuming tasks in the assembly process. The screenshot above shows that CompileStoryboard , CompileXIB , CompileSwiftSources, and the PhaseScriptExecution phases took up most of the build time. Xcode was able to complete part of the tasks in parallel, so the assembly is completed much faster than the necessary time to complete the task of each of the commands.

We can get a build time summary for Clean build using xcodebuild with the option - buildWithTimingSummary : Now let's get the same metrics for Incremental build. It should be noted that the Incremental build time is completely dependent on the files being modified in the project. To get consistent results, you can modify one file and compile the project. Unlike Buck or Bazel systems, Xcode uses timestamps to determine what has changed and what needs to be rebuilt. We can update timestamp using touch:

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]



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]

Type Verification Warnings


When compilation time is a bottleneck, we can get more information by setting Other Swift Flags to the project build options in Xcode. When the flag is enabled, Xcode generates a warning for any function or expression that takes more than 100ms to determine the type of variables:

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

image

Now you know the code with which the Swift compiler has problems and now you can try to improve it.

Compiler Diagnostic Options


The Swift compiler has many built-in diagnostic options that you can use to profile the assembly.

  • -driver-time-compilation - a high level of synchronization of tasks performed by the driver.
  • -Xfrontend -debug-time-compilation — .
  • -Xfrontend -debug-time-function-bodies – .
  • -Xfrontend -debug-time-expression-type-checking – .

Let's use the -debug-time-compilation checkbox to get information about the slowest files when compiling: As you can see, it took 25 seconds to compile SettingsNewslettersCellViewModel.swift . From the assembly log, you can get additional information about the file compilation time: Now it is obvious that Type checking and Semantic analysis are the most time-consuming work. Let's go ahead and list the slowest functions and expressions in the Type Checking step :

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



===-------------------------------------------------------------------------===
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



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

In the same way, we profiled our assembly and found out that at the stage of type checking there are rather bottlenecks. As a next step, you can take a look at the functions and expressions listed above and try to optimize type inference .

Target Build Time


It is advisable to measure the assembly time of the target separately and display them on the graph. You can help understand which targets are being collected or can be collected in parallel. To do this, you can use the xcode-build-time-rendering tool. Let's set it as RubyGem: After the installation is complete, run the following command to enter the timestamp log in the Run Script Build Phase of your targets: Then compile the project and report using: As a result, you should get a nice Gantt chart by build time that shows the time assemblies of all your targets:

gem install xcode-build-times



xcode-build-times install



xcode-build-times generate



image

Aggregated Metrics


It would be great to aggregate the various indicators mentioned above. XCLogParser is a great tool that can help you with this. It is a log analyzer for xcactivitylog generated by Xcode, and gives a lot of information about the build time for each module and file in the project, warnings, errors and unit test results. You can install it by cloning the repository and launching through the command line: This is a report created for the Kickstarter iOS project:

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



image

Automation


It should be noted that the assembly time metrics depend on the hardware and their use. You can use your equipment for experiments. The best option would be to automate the process as much as possible and use dedicated CI equipment for daily metrics. Ultimately, they can be monitored on the dashboard and notified of worsening build times using Slack.

Conclusion


Reducing the build time of a project is critical to improving developer productivity. Today we learned how to measure the build time of a project and get some metrics for analysis ...

In the next publication, we will consider methods that can be used to reduce build time.

Thanks for reading!

All Articles