📜 ⬆️ ⬇️

Swift and compile time

The post is based on the article medium.com/@RobertGummesson/regarding-swift-build-time-optimizations-fc92cdd91e31 + small changes / additions, that is, the text comes from my face, not the third.

Everyone is familiar with the situation after changing the file (s) or simply reopening the project:



')


It must be said that I did not notice any particular problem with compilation when I wrote on Objective-C, but everything changed with the advent of the swift to the masses. I was partially responsible for the CI server for building iOS projects. So, swift projects were going unbearably slow. Even developers like to drag pods (cocoapods dependencies) on each sneeze into their swift projects, sometimes in insane amounts. So, the compilation of all this mixture could take several minutes, although the project itself consisted of literally a couple of dozen classes. Well, as it is clear, the language is new, at the compilation stage much more checks occur than in the ObjC, it’s foolish to expect it to work faster (yes, swift is a strongly typed language and everything should be explicitly declared as much as possible (as in Java, for example), in contrast to ObjC, where everything is not so strict). With each release, swift developers promise x times faster compilation speeds. Well, it seems, indeed, build 2.0, and only then 2.2 it began to work faster, but now the 3.0 version is already on the nose (end of year).

The compiler is a compiler, but it turns out that the time to build a project can be drastically increased with just a few lines of code. Some examples are taken from the above article, some are self-written (I didn’t believe the author and wanted to check it out myself). Time measured through

time swiftc -Onone file.swift 


A list of things that can slow down your compiler.



Careless use of Nil Coalescing Operator



 func defIfNil(string: String?) -> String { return string ?? "default value" } 


Operator ?? expands Optional , if there is something there, then returns a value otherwise the expression is executed after ?? . If we connect several of these operators in succession, we can get an increase in compile time by an order of magnitude (not mistaken, an order of magnitude :)).

 //     0,09  func fn() -> Int { let a: Int? = nil let b: Int? = nil let c: Int? = nil var res: Int = 999 if let a = a { res += a } if let b = b { res += b } if let c = c { res += c } return res } 


 //   3,65  func fn() -> Int { let a: Int? = nil let b: Int? = nil let c: Int? = nil return 999 + (a ?? 0) + (b ?? 0) + (c ?? 0) } 


Yes, 40 times :). But my observations have shown that the problem occurs only if you use more than two such operators in the same expression, that is:

 return 999 + (a ?? 0) + (b ?? 0) 

will already be ok.

Array combining



I sometimes write scripts in Ruby, chop up a terrific language, where a large number of operations can be put in a couple of lines of code, without losing readability. Especially like working with arrays. That is, it is quite a common code for Ruby for me:

 res = ar1.filter { ... } + ar2.map { ... } + ar3.flatMap { ... } 


If in this style we write swift, then it can spoil the compilation time very much, although the language constructs allow writing this way. Well, as if everyone is now only talking about immobility, therefore creating a mutable array is not comme il faut and generally bad form, pff. But what do we get if we write like that?

 //  0,15  func fn() -> [Int] { let ar1 = (1...50).map { $0 }.filter { $0 % 2 == 0 } let ar2 = [4, 8, 15, 16, 23, 42].map { $0 * 2 } var ar3 = (1..<20).map { $0 } ar3.appendContentsOf(ar1) ar3.appendContentsOf(ar2) return ar3 } 


 //  2,86  func fn() -> [Int] { let ar1 = (1...50).map { $0 } let ar2 = [4, 8, 15, 16, 23, 42] return (1..<20).map { $0 } + ar1.filter { $0 % 2 == 0 } + ar2.map { $0 * 2 } } 


The difference is almost 20 times. But with arrays we work in every second method. But It is worth noting that I got this compiler behavior if I summarize more than two arrays in one expression, that is:

 return ar1.filter { $0 % 2 == 0 } + ar2.map { $0 * 2 } 

already ok

Ternary operator



The author of the original post gives an example, from which it follows that you should not use complex expressions with the ternary operator:

 // Build time: 0,24  let labelNames = type == 0 ? (1...5).map{type0ToString($0)} : (0...2).map{type1ToString($0)} // Build time: 0,017  var labelNames: [String] if type == 0 { labelNames = (1...5).map{type0ToString($0)} } else { labelNames = (0...2).map{type1ToString($0)} } 


Excess caste



Here the author simply removed the extra castes in CGFloat and gained significant speed in compiling the file:

 // Build time: 3,43  return CGFloat(M_PI) * (CGFloat((hour + hourDelta + CGFloat(minute + minuteDelta) / 60) * 5) - 15) * unit / 180 // Build time: 0,003  return CGFloat(M_PI) * ((hour + hourDelta + (minute + minuteDelta) / 60) * 5 - 15) * unit / 180 


Complex expressions



Here the author means, a mixture of local variables, class variables and class instances along with functions:

 // Build time: 1,43  let expansion = a - b - c + round(d * 0.66) + e // Build time: 0,035  let expansion = a - b - c + d * 0.66 + e 


True, the author of the original post did not understand why.




In general, we can summarize that using complex features / language constructs is great, but it is worth remembering that all this syntactic sugar can significantly affect both the compile time and runtime itself. They say, if you write on a rock under the android, then due to all this syntactic sugar, you can very quickly rest against the limit of the number of methods and you will need to resort to extra gestures.

ps

One of the developers of the swift language gave a small answer to the original author for this post, that in swift 3 many improvements were made in this direction:

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


All Articles