📜 ⬆️ ⬇️

Fight for build-in iOS applications

A little more than a month ago we released the Tinkoff Investments iOS application. The application is written entirely in Swift, but has some Objective-C dependencies. The product quickly began to grow into new functionality, and at the same time, the project build time increased significantly. When we came to the conclusion that after the clean or significant revisions the project had been going for more than six minutes, we realized that change was necessary.

image

On the Internet, many effective and not very useful ways were found to speed up project build time. We were especially interested in the build time of the debug-version, because it became increasingly difficult to work. Below I will talk about the methods that we tested in the framework of solving the problem, and the results we achieved. I want to note that a long build time may depend on various factors, therefore, the methods for each project are different.

1. Code sections difficult to compile.


Since Swift is still young, some sweet syntactic constructs can cause compiler misunderstanding. To assess the most difficult to compile functions, you can use the Swift analyzer built into the compiler. The easiest way to get a report is to build the project from the console with this command:

xcodebuild -workspace App.xcworkspace -scheme App clean build OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-function-bodies" | grep .[0-9]ms | grep -v ^0.[0-9]ms | sort -nr > functions_build_analysis.txt 

where “App.xcworkspace” is the name of the file of the workspace of your project, “App” is the name of the scheme according to which you need to build.
')
We pass the "-Xfrontend -debug-time-function-bodies" flags to debug the compilation process and take into account the time to compile each function. With grep, we select the lines containing the compile time, then output the sorted result to the functions_build_analysis.txt file.

Using this report, we found several difficult to compile functions, one of which was collected for 17 seconds, and the other - 6. The main reason for such deplorable results is the use of Nil Coalescing in the object constructor. The code had the following constructions:

 let object = Object(param1: param1Value ?? defaultParam1Value, param2: param2Value ?? defaultParam2Value) 

We carried out the calculation of the parameter value on a separate line above, and the problem was solved - the compile time of the functions was reduced to 300 milliseconds.

This is not the only surprise that the Swift compiler can give you. The main problems of a long assembly of individual functions are associated with the definition of variable types. Most often this is due to the use of the "??", "?:" Operators in object constructors, dictionaries, arrays, as well as in the concatenation of strings and arrays. You can read an article with interesting observations about speeding up build time through code refactoring.

The total gain that we were able to get from the manipulations with the code is 30 seconds, this was already a good achievement.

2. Build only the selected architecture for Debug builds.




In a debug build, the project is compiled only for the device architecture selected for debugging. That is, choosing Yes here, we theoretically speed up the build time by half.

Since in our project this flag was already set to Yes, we did not win at this point. But for the sake of experiment, we tried the assembly with the flag set in No. For this, I had to tinker with Pods, because they, too, were ready to provide the compiled code only for the active architecture. The project build time in the end was 10 minutes, 21 seconds, which is almost two times longer than the original.

3. Whole Module Optimization.


The Swift compiler has a flag called “ -whole-module-optimization ”. He is responsible for how the files will be processed during compilation: whether they will be compiled one by one or assembled at once into a module. In Xcode, you can control this flag using the “Optimization Level” section, but by default only these options are available to us:



Using Whole Module Optimization significantly reduces the compile time for debug builds. But along with this flag, we add the flag “-O”, which includes the SIL optimizer, and a problem arises - the project ceases to be debugged. In the console, we see the following:

App was compiled with optimization - stepping may behave oddly; variables may not be available.

To preserve the ability to build a whole module at once and disable optimization, you can add the “-Onone” flag in the “Other Swift Flags” section. As a result, for debugging, we get the assembly, as quickly as possible going by turning off all sorts of optimizations. In our project, this gave amazing results - debug build speed increased almost 3 times.

4. Precompiled Bridging Headers.


There is another compiler flag that helps reduce compile time. But it only works for builds without the “-whole-module-optimization” flag and, rather, it can be useful for release builds. This is the " -enable-bridging-pch " flag.



It helps not in all cases, but only in projects with Bridging headers from Objective-C. The effect is that each time during the build, the compiler does not rebuild the bridging table of Objective-C methods in Swift.

For our project with the “-whole-module-optimization” flag turned off and the “-enable-bridging-pch” flag turned on, the gain in time was about 15% .

Results


According to the results of the study, two main ways to speed up the compilation of your Debug build are: optimizing the code itself for the compiler and using the "whole-module-optimization" flag. We managed to reduce the net build time of the project (clean build) from 6 minutes to 1 minute 20 seconds, half of which is the build of third-party dependencies. If you have your own experience with the Swift compiler, share it in the comments.

PS: the iron on which the tests were conducted:
Mac mini (Late 2012)
2.3 GHz Intel Core i7
16 GB 1600 MHz DDR3
250 GB SSD

Source: https://habr.com/ru/post/331044/


All Articles