Why is Swift compile time so slow?

2019-01-03 11:18发布

I'm using Xcode 6 Beta 6.

This is something that's been bugging me for some time now, but it's reaching a point where it's barely usable now.

My project is starting to have a decent size of 65 Swift files and a few bridged Objective-C files (which are really not the cause of the problem).

It seems like any slight modification to any Swift file (like adding a simple white space in a class that's barely used in the app) will cause the entire Swift files for the specified target to be recompiled.

After a deeper investigation, I've found that what is taking pretty much 100% of the compiler time is the CompileSwift phase where Xcode runs the swiftc command on all Swift files of your target.

I did some further investigation, and if I only keep the app delegate with a default controller the compilation is very fast, but as I was adding more and more of my project files, the compile time was starting to get really slow.

Now with only 65 source files, it takes about 8/10 seconds to compile each time. Not very swift at all.

I haven't seen any post talking about this issue except this one, but it was an old version of Xcode 6. So I'm wondering if I'm the only one in that case.

UPDATE

I've checked a few Swift projects on GitHub like Alamofire, Euler and CryptoSwift, but none of them had enough Swift files to actually compare. The only project I found that was starting a have decent size was SwiftHN, and even though it only had a dozen source files I was still able to verify the same thing, one simple space and the whole project needed recompilation which was starting to take a little time (2/3 seconds).

Compared to Objective-C code where both analyzer and compilation are blazing fast, this really feels like Swift will never be able to handle big projects, but please tell me I'm wrong.

UPDATE With Xcode 6 Beta 7

Still no improvement whatsoever. This is starting to get ridiculous. With the lack of #import in Swift, I really don't see how Apple will ever be able to optimize this.

UPDATE With Xcode 6.3 and Swift 1.2

Apple has added incremental builds (and many other compiler optimizations). You have to migrate your code to Swift 1.2 to see those benefits, but Apple added a tool in Xcode 6.3 to help you do so:

Enter image description here

HOWEVER

Don't rejoice too quickly as I did. The graph solver that they use to make the build incremental is not very well optimised yet.

Indeed first, it doesn't look at function signature changes so if you add a space in the block of one method, all files depending on that class will be recompiled.

Second, it seems to create the tree based on the files that were recompiled even if a change doesn't affect them. For example, if you move these three classes into different files

class FileA: NSObject {
    var foo:String?
}
class FileB: NSObject {
    var bar:FileA?
}
class FileC: NSObject {
    var baz:FileB?
}

Now if you modify FileA, the compiler will obviously mark FileA to be recompiled. It will also recompile FileB (that would be OK based on the changes to FileA), but also FileC because FileB is recompiled, and that is pretty bad because FileC never uses FileA here.

So I hope they improve that dependency tree solver... I've opened a radar with this sample code.

UPDATE With Xcode 7 beta 5 and Swift 2.0

Yesterday Apple released the beta 5 and inside the release notes we could see:

Swift Language & Compiler • Incremental builds: changing just the body of a function should no longer cause dependent files to be rebuilt. (15352929)

I have given it a try and I must say it is working really (really !) well now. They greatly optimized the incremental builds in swift.

I highly recommend you create a swift2.0 branch and keep your code up to date using XCode 7 beta 5. You will be pleased by the enhancements of the compiler (however I'd say the global state of XCode 7 is still slow & buggy)

UPDATE With Xcode 8.2

It's been a while since my last update on this issue so here it is.

Our app is now about 20k lines of almost exclusively Swift code, which is decent but not outstanding. It underwent swift 2 and than swift 3 migration. It takes about 5/6m to compile on a mid 2014 Macbook pro (2.5 GHz Intel Core i7) which is okay on a clean build.

However the incremental build is still a joke despite Apple claiming that:

Xcode will not rebuild an entire target when only small changes have occurred. (28892475)

Obviously I think many of us just laughed after checking out this nonsense (adding one private (private!) property to any file of my project will recompile the whole thing...)

I would like to point you guys to this thread on Apple developer forums which has some more information about the issue (as well as appreciated Apple dev communication on the matter once in a while)

Basically people have come up with a few things to try to improve the incremental build:

  1. Add a HEADER_MAP_USES_VFS project setting set to true
  2. Disable Find implicit dependencies from your scheme
  3. Create a new project and move your files hierarchy to the new one.

I will try solution 3 but solution 1/2 didn't work for us.

What's ironically funny in this whole situation is that looking at the first post on this issue we were using Xcode 6 with I believe swift 1 or swift 1.1 code when we reached the first compilations slugginess and now about two years later despite actual improvements from Apple the situation is just as bad as it was with Xcode 6. How ironic.

I actually REALLY regret choosing Swift over Obj/C for our project because of the daily frustration it involves. (I even switch to AppCode but that's another story)

Anyways I see this SO post has 32k+ views and 143 ups as of this writing so I guess I'm not the only one. Hang in there guys despite being pessimistic over this situation there might be some light at the end of the tunnel.

If you have the time (and courage!) I guess Apple welcomes radar about this.

Til next time ! Cheers

UPDATE With Xcode 9

Stumble upon this today. Xcode quietly introduced a new build system to improve on the current awful performance. You have to enable it through the workspace settings.

enter image description here

Have given a try yet but will update this post after it's done. Looks promising though.

21条回答
聊天终结者
2楼-- · 2019-01-03 11:56

Also make sure that when compiling for debug (either Swift or Objective-C), you set to Build Active Architecture Only:

enter image description here

查看更多
干净又极端
3楼-- · 2019-01-03 11:57

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.

查看更多
一夜七次
4楼-- · 2019-01-03 11:57

Since all this stuff is in Beta, and since the Swift compiler is (at least as of today) not open, I guess there is no real answer to your question.

First of all, comparing Objective-C to Swift compiler is somehow cruel. Swift is still in Beta, and I am sure Apple is working in providing functionality and fixing bugs, more than providing lightning speed (you don't start building a house by buying the furniture). I guess Apple will optimize the compiler in due time.

If for some reason all source files have to be compiled completey, an option might be to create separated modules/libraries. But this option is not yet possible, as Swift cannot allow libraries until the language is stable.

My guess is that they will optimize the compiler. For the same reason that we cannot create pre-compiled modules, it could well be that the compiler needs to compile everything from scratch. But once the language reaches a stable version and the binaries' format is not changing anymore, we will be able to create our libraries, and maybe (?) the compiler will also be able to optimize its work.

Just guessing, though, for only Apple knows...

查看更多
Evening l夕情丶
5楼-- · 2019-01-03 11:59

The compiler spends a lot of time inferring and checking the types. So adding type annotations helps the compiler a lot.

If you have lots of chained function calls like

let sum = [1,2,3].map({String($0)}).flatMap({Float($0)}).reduce(0, combine: +)

Then the compiler takes a while to figure out what the type of sum should be. Adding the type helps. What also helps is pulling the intermittent steps into separate variables.

let numbers: [Int] = [1,2,3]
let strings: [String] = sum.map({String($0)})
let floats: [Float] = strings.flatMap({Float($0)})
let sum: Float = floats.reduce(0, combine: +)

Especially for numeric types CGFloat, Int it can help a lot. A literal number like 2 can represent many different numeric types. So the compiler needs to figure out from the context which one it is.

Functions that take a lot of time to look up like + should also be avoided. Using several + to concatenate several arrays is slow because the compiler needs to figure out which implementation of + should be called for each +. So use a var a: [Foo] with append() instead if possible.

You can add a warning to detect which functions are slow to compile in Xcode.

In Build Settings for your target search for Other Swift Flags and add

-Xfrontend -warn-long-function-bodies=100

to warn for every function that takes longer than 100 ms to compile.

查看更多
叛逆
6楼-- · 2019-01-03 12:01

For the projects that mix Objective-C and Swift code, we can set -enable-bridging-pch in Other Swift Flags. With this, bridging header is parsed only once, and the result (a temporary “precompiled header” or “PCH” file) is cached and reused across all Swift files in the target. Apple claimed it decreases build time by 30%. Reference Link:

NOTE: This works for Swift 3.1 and above only.

查看更多
叛逆
7楼-- · 2019-01-03 12:02

Nothing worked for me in Xcode 6.3.1 - when I added arround 100 Swift files Xcode randomly hanged on build and/or indexing. I've tried a modular option with no success.

Installing and using Xcode 6.4 Beta actually worked for me.

查看更多
登录 后发表回答