• Regarding Swift build time Optimizations
  • The Nuggets translation Project
  • Translator: Yang Longlong
  • Proofread by: Shen Guanhua, Jack King

Some thoughts on Swift compile time performance optimization

Last week, AFTER reading an excellent post by @Nickoneill promising Up Slow Swift Build Times, I realized that it’s not too hard to read Swift code from a slightly different perspective.

What was once a clean line of code now raises a new question — should it be refactored to nine lines for faster compilation? (The nil coalescing operator is an example.) Which is heavier? Clean code or compiler friendly code? I think it depends on the size of the project and what the developer wants.

But wait… There is an Xcode plug-in

Before I go over some examples, it first occurred to me that extracting log information manually can be time-consuming. It would have been relatively easy to implement through a command-line tool, but I took it a step further by integrating it as an Xcode plug-in.

In this case, the original goal was just to identify and fix the most time-consuming areas of the code, but now I think it’s a process that must be iterative. This way I can build the code more efficiently and prevent time-consuming functions from appearing in the project.

A lot of surprise

I often jump from Git branch to Git branch, and I waste my life waiting for a warm and slow project to compile. So I thought for a long time that a toy project (about 20,000 lines of Swift code) would take that long to compile.

When I found out what was causing it to be so slow, I have to admit I was shocked that a line of code would take a few seconds to compile.

Let’s look at a few examples.

Nil merge operator

Compilers definitely don’t like the first approach here. After unrolling the following two abbreviations, the build time was reduced by 99.4%.

// build time: 5238.3ms return CGSize(width: sie.width + (rightView? .bounds.width ?? 0) + (leftView? .bounds.width ?? 0) + 22, height: bounds. Height) CGFloat = 22 if let rightView = rightView { padding += rightView.bounds.width } if let leftView = leftView { padding += leftView.bounds.width } return CGSizeMake(size.width + padding, bounds.height)Copy the code

ArrayOfStuff + [Stuff]

This looks like this:

Return ArrayOfStuff + [Stuff] // Instead of ArrayOfStuff. Append (Stuff) return ArrayOfStuffCopy the code

I do this a lot, and it affects the timing of each build. Here is the worst example, with a rewrite that reduced build time by 97.9%.

// Build time: 1250.3ms let systemOptions = [7, 14, 30, -1] let systemNames = (0... 2).map{ String(format: localizedFormat, systemOptions[$0]) } + [NSLocalizedString("everything", comment: "")] // Some code in-between labelNames = Array(systemNames[0..<count]) + [systemNames.last!] Ms let systemOptions = [7, 14, 30, -1] var systemNames = systemOptions.droplast ().map{String(format: localizedFormat, $0) } systemNames.append(NSLocalizedString("everything", comment: "")) // Some code in-between labelNames = Array(systemNames[0..<count]) labelNames.append(systemNames.last!)Copy the code

Ternary operator

The build time was reduced by 92.9% simply by replacing the ternary operator with an if else statement. If you replace the map function with a for loop, it reduces the map function by another 75% (but my eyes can’t bear it).

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

Convert CGFloat to CGFloat

What I’m saying here is not necessarily true. Variables already use CGFloat and some parentheses are redundant. After eliminating these redundancies, build times were reduced by 99.9%.

// Build time: 3431.7ms return CGFloat(M_PI) * (CGFloat((hour + hourDelta + CGFloat(minute + minuteDelta) / 60) * 5) -15) * unit / 180 // Build time: 3.0ms return CGFloat(M_PI) * ((hour + hourDelta + (minute + minuteDelta) / 60) * 5-15) * unit / 180Copy the code

Round()

This is a very strange example. In the following example, the variable is a mixture of a local variable and an instance variable. The problem may not be the rounding itself, but the way the code is combined. Removing the rounding method reduced build time by approximately 97.6%.

// Build time: 1433.7ms Let expansion = a -- b -- C + round(d * 0.66) + e 34.7ms let expansion = a -- B -- C + D * 0.66 + ECopy the code

Note: All tests were performed on the MacBook Air (13-Inch, Mid 2013).

Try it out

Whether or not you’re dealing with build times that are too long, it’s very useful to write compilers friendly code. I’m sure you’ll find some surprises in it for yourself. For reference, here is the complete code, which can compile in 5 seconds in my project…

import UIKit class CMExpandingTextField: UITextField { func textFieldEditingChanged() { invalidateIntrinsicContentSize() } override func intrinsicContentSize() -> CGSize { if isFirstResponder(), let text = text { let size = text.sizeWithAttributes(typingAttributes) return CGSize(width: size.width + (rightView? .bounds.width ?? 0) + (leftView? .bounds.width ?? 0) + 22, height: bounds.height) } return super.intrinsicContentSize() } }Copy the code