📜 ⬆️ ⬇️

Tuning Swift compiler. Part 1

image


Overview of Swift 3 compiler and ways to speed it up. Part 1.
Debunking existing myths. Opinion on autocompletion problems in Xcode.



Preface:


Our company is engaged in the development of turnkey mobile applications. Many of our iOS developers speak Objective-C better than Russian, their Cocoa girl, and they sleep in an embrace with an iPhone ... and now we suddenly write on Swift.


I will not talk about the different shoals of syntax, funny "Segmentation Fault: 11", periodically dying backlight, it's all already known. Let it hurt, but bearable.
But there is something truly killing business, not just discomforting. Slow compiler. Yes, yes, this is not just a loud headline.
When projects of the same size on Obj-C and Swift are assembled with a fourfold time difference. When adding one method starts rebuilding half of the entire code. When compiler errors generally disable it, this is really a killing of developer time. And as you know: time is money.


There are two options: continue to whine and endure, or to resolve the issue. We chose the latter.


The invention of the bicycle


Before plunging knee-deep in Swift, we pre-rustled across the expanses of the Internet for existing research on this subject. Fortunately, a good article was found in Russian and the original one in English .


So why another produce? And then, that, firstly, it was all before the third Swift, secondly, some of the statements in the article are not entirely true, and it would be nice to supplement the list of cunning places. What we do.


The material here is not on one article, so I will lay out gradually.
And besides the compilation speed itself, there is still a performance factor at runtime, which should also be covered.


Let's start with what was already known, but just check for relevance in Swift 3.


Nil Coalescing Operator


My favorite feature is Swift, sugar optional. Something like a nil-safe message in Obj-C.


Take an example from past articles. Now you will understand why they are not quite correct:


let left: UIView? = UIView() let right: UIView? = UIView() let width: CGFloat = 10 let height: CGFloat = 10 let size = CGSize(width: width + (left?.bounds.width ?? 0) + (right?.bounds.width ?? 0) + 22, height: height) 

Compile time : 12 seconds! Buddy, do you have a third stump?
Even worse than it was in Swift 2.2.


I want to say: "Wow, Apple, what the?", But do not rush to conclusions. Let's optimize this code a bit by breaking the long expression into several small ones:


 let firstPart = left?.bounds.width ?? 0 + width let secondPart = right?.bounds.width ?? 0 + 22 let requiredWidth = firstPart + secondPart let size = CGSize(width: requiredWidth, height: height) 

Compile time : 30 ms. (milliseconds)


It turns out, it's not at all evil optional?
But no, that would be too easy. Let's complicate the task:


 class A { var b: B? = B() } class B { var c: C? = C() } class C { var d: D? = D() } class D { var value: CGFloat? = 10 } ... let left: A? = A() let right: A? = A() let width: CGFloat = 10 let height: CGFloat = 10 //  ! let firstPart = left?.b?.c?.d?.value ?? 0 + width let secondPart = right?.b?.c?.d?.value ?? 0 + 22 let requiredWidth = firstPart + secondPart let size = CGSize(width: requiredWidth, height: height) 

Compile time : 35 ms.


Conclusion : The Nil Coalescing Operator is all sterile, you can use.
But then what was the problem?


It is not difficult to guess that the root of evil lurks in long expressions. The author of the Russian article casually mentioned that the problem with the nil coalescing operator is reproduced only in complex operations, but, unfortunately, I did not focus on this.


The rule is as follows : the compiler causes constipation of an expression with several complex components. That is, those that are not just variables, but also perform some actions. But you can add variables as much as you want.


You probably say, "Where are the proofs, Billy?"


Good. Then we take the previous code, but we will not split it into sub-operations:


 let requiredWidth = left?.b?.c?.d?.value ?? 0 + right?.b?.c?.d?.value ?? 0 + width + 22 let size = CGSize(width: requiredWidth, height: height) 

The result did not have to wait long (had to):
image


I quote, if you could not read from the screen: "Consider the difference in distinct sub-expressions".


Translation: "The expression was too complicated to solve in a reasonable time. Break the formula into separate sub-expressions."


PM


Unexpected over-effect

Further is an observation without a theoretical basis.


Many people have noticed that auto-completion falls off regularly in Xcode. This usually happens at the time of the background compilation. If you have written something like an expression that causes an " Expression was too complex ", then the hints will immediately die.


This can be easily verified. Take the same method and start writing self.view to get a hint:
image


And then add our killer expression. Everything, you will not receive any more hints, even if it is hard to knock on ctrl + space
image


It is treated by running an explicit compilation and elimination of the cancer code.


Go ahead.


Ternary operator


The article also highlights the problems of the ternary operator. Compile time of the code can be seen in the comments:


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

By the way, I have no such method as type0ToString in the SDK. I replaced it with a simplified version, no difference:


 let labelNames = type == 0 ? (1...5).map{String($0)} : (0...2).map{String($0)} 

Compile time : 260 ms. While everything is confirmed.


But it seems to me that the ternary operator is unjustly accused. Let's try again to break the formula into separate expressions, but without using if-else:


 let first = (1...5).map{String($0)} let second = (0...2).map{String($0)} let labelNames = type == 0 ? first : second 

Compile time : 45 ms


But this is not the limit. Simplify even more:


 let first = 4 let second = 5 let labelNames = type == 0 ? first : second 

Compile time : 7 ms.


Verdict: The ternary operator is justified.


Some more amnesties


Round () operation:


 // Build time: 1433.7ms let expansion = a - b - c + round(d * 0.66) + e 

Compile time : 6ms


Addition of arrays:


 // Build time Swift 2.2: 1250.3ms // Build time Swift 3.0: 92.7ms ArrayOfStuff + [Stuff] 

Compile time : 19ms


And the sweetest:


 let myCompany = [ "employees": [ "employee 1": ["attribute": "value"], "employee 2": ["attribute": "value"], "employee 3": ["attribute": "value"], "employee 4": ["attribute": "value"], "employee 5": ["attribute": "value"], "employee 6": ["attribute": "value"], "employee 7": ["attribute": "value"], "employee 8": ["attribute": "value"], "employee 9": ["attribute": "value"], "employee 10": ["attribute": "value"], "employee 11": ["attribute": "value"], "employee 12": ["attribute": "value"], "employee 13": ["attribute": "value"], "employee 14": ["attribute": "value"], "employee 15": ["attribute": "value"], "employee 16": ["attribute": "value"], "employee 17": ["attribute": "value"], "employee 18": ["attribute": "value"], "employee 19": ["attribute": "value"], "employee 20": ["attribute": "value"], ] ] 

Compile time : 86 ms. It could be better, but at least not 12 hours .




On this first part I would like to finish. In it, we have debunked the myths about the optional and ternary operators, the addition of arrays, and some functions. We learned about one of the reasons for the hangs of autocompletion, and also found out that the Swift compilation is most complicated by complex formulas. Hope it was helpful.


The second part .


')

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


All Articles