Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is Swift compile time so slow?

People also ask

Why does Xcode take so long to compile?

Xcode has inbuilt features that allow you to identify functions and expressions that are causing longer compile times. You can specify a compile time limit and identify areas in your codebase that exceed this limit. Open up your project's build settings and add the following flags to your Other Swift Flags .


Well, it turned out that Rob Napier was right. It was one single file (actually one method) that was causing the compiler to go berzek.

Now don't get me wrong. Swift does recompile all your files each time, but the great thing now, is that Apple added real-time compilation feedback over the files it compiles, so Xcode 6 GM now shows which Swift files are being compiled and the status of compilation in real time as you can see in this screenshot:

Enter image description here

So this comes very handy to know which of your files is taking so long. In my case it was this piece of code:

var dic = super.json().mutableCopy() as NSMutableDictionary
dic.addEntriesFromDictionary([
        "url" : self.url?.absoluteString ?? "",
        "title" : self.title ?? ""
        ])

return dic.copy() as NSDictionary

because the property title was of type var title:String? and not NSString. The compiler was going crazy when adding it to the NSMutableDictionary.

Changing it to:

var dic = super.json().mutableCopy() as NSMutableDictionary
dic.addEntriesFromDictionary([
        "url" : self.url?.absoluteString ?? "",
        "title" : NSString(string: self.title ?? "")
        ])

return dic.copy() as NSDictionary

made the compilation go from 10/15 seconds (maybe even more) down to a single second... amazing.


We've tried quite a few things to combat this as we have around 100k lines of Swift code and 300k lines of ObjC code.

Our first step was to optimize all functions according to the function compile times output (eg as described here https://thatthinginswift.com/debug-long-compile-times-swift/)

Next we wrote a script to merge all the swift files into one file, this breaks access levels but it brought our compile time from 5-6min to ~1minute.

This is now defunct because we asked Apple about this and they advised we should do the following:

  1. Turn on 'whole module optimization' in the 'Swift Compiler - Code Generation' build setting. Select 'Fast, Whole Module Optimization'

enter image description here

  1. In 'Swift Compiler - Custom Flags', for your development builds, add '-Onone'

enter image description here enter image description here

When these flags are set, the compiler will compile all Swift files in one step. We found with our merge script this is much faster than compiling files individually. However, without the '-Onone' override, it will also optimize the whole module, which is slower. When we set the '-Onone' flag in the other Swift flags, it stops the optimization, but it doesn't stop compiling all Swift files in one step.

For more info on whole module optimization, check out Apple's blog post here - https://swift.org/blog/whole-module-optimizations/

We've found these settings allow our Swift code to compile in 30 seconds :-) I've no evidence of how it would work on other projects, but I suggest giving it a try if Swift compile times are still an issue for you.

Note for your App store builds, you should leave the '-Onone' flag out, as the optimization is recommended for production builds.


It likely has little to do with the size of your project. It's probably some specific piece of code, possibly even just one line. You can test this out by trying to compile one file at a time rather than the whole project. Or try watching the build logs to see which file is taking so long.

As an example of the kinds of code that can cause trouble, this 38-line gist takes more than a minute to compile in beta7. All of it is caused by this one block:

let pipeResult =
seq |> filter~~ { $0 % 2 == 0 }
  |> sorted~~ { $1 < $0 }
  |> map~~ { $0.description }
  |> joinedWithCommas

Simplify that by just a line or two and it compiles almost instantly. The trouble is something about this is causing exponential growth (possibly factorial growth) in the compiler. Obviously that's not ideal, and if you can isolate such situations, you should open radars to help get those issues cleaned up.


If you're trying to identify specific files that slow down your compile time, you could try compiling it from your command line via xctool which will give you compile times file by file.

The thing to note is that, by default, it builds 2 files concurrently per each CPU core, and will not give you the "net" elapsed time, but the absolute "user" time. This way all the timings even out between parallelized files and look very similar.

To overcome this, set the -jobs flag to 1, so that it doesn't parallelize file builds. It will take longer, but in the end you'll have "net" compile times that you can compare file by file.

This is an example command that should do the trick:

xctool -workspace <your_workspace> -scheme <your_scheme> -jobs 1 build

The output of the "Compile Swift files" phase would be something like:

...
   ✓ Compile EntityObserver.swift (1623 ms)
   ✓ Compile Session.swift (1526 ms)
   ✓ Compile SearchComposer.swift (1556 ms)
...

From this output you can quickly identify which files are taking longer than others to compile. Moreover, you can determine with high accuracy whether your refactorings (explicit casts, type hints, etc...) are lowering compile times for specific files or not.

NOTE: technically you could also do it with xcodebuild but the output is incredibly verbose and hard to consume.


In my case, Xcode 7 did no difference at all. I had multiple functions requiring several seconds to compile.

Example

// Build time: 5238.3ms
return CGSize(width: size.width + (rightView?.bounds.width ?? 0) + (leftView?.bounds.width ?? 0) + 22, height: bounds.height)

After unwrapping the optionals, the build time dropped by 99.4%.

// Build time: 32.4ms
var padding: 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)

See more examples in this post and this post.

Build Time Analyzer for Xcode

I developed an Xcode plug-in which may come in handy for anyone experiencing these issues.

image

There appears to be improvements coming in Swift 3 so hopefully we'll see our Swift code compile quicker then.