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 12:12

Swift compile time was improved in new Xcode 6.3

Compiler improvements

The Swift 1.2 compiler was engineered to be more stable and to improve performance in every way. These changes also provide a better experience when working with Swift in Xcode. Some of the most visible improvements include:

Incremental builds

Source files that haven’t changed will no longer be re-compiled by default, which will significantly improve build times for most common cases. Larger structural changes to your code may still require multiple files to be rebuilt.

Faster executables

Debug builds produce binaries that run considerably faster, and new optimizations deliver even better Release build performance.

Better compiler diagnostics

Clearer error and warning messages, along with new Fix-its, make it easier to write proper Swift 1.2 code.

Stability improvements

The most common compiler crashes have been fixed. You should also see fewer SourceKit warnings within the Xcode editor.

查看更多
做个烂人
3楼-- · 2019-01-03 12:13

Unfortunately, the Swift compiler is still not optimized for fast and incremental compilation (as of Xcode 6.3 beta). Meanwhile you can use some of the following techniques to improve Swift compile time:

  • Split the app into Frameworks to reduce recompilation impact. But be aware that you must avoid cyclic dependencies in your app. For futher info on this topic check this post: http://bits.citrusbyte.com/improving-swift-compile-time/

  • Use Swift for parts of your project that are quite stable and don't change often. For other areas where you need to change very often or areas that require lot of compile/run iterations to be complete (almost any UI related stuff), better use Objective-C with a mix-and-match approach.

  • Try runtime code injection with 'Injection for Xcode'

  • Use roopc method: http://roopc.net/posts/2014/speeding-up-swift-builds/

  • Relief the swift type inference engine by giving some hints with explicit casts.

查看更多
贼婆χ
4楼-- · 2019-01-03 12:15

Rebooting my Mac did wonders for this issue. I went from 15 minute builds to 30 second builds just by rebooting.

查看更多
贪生不怕死
5楼-- · 2019-01-03 12:16

For debug and testing, make sure to use the following settings to cut the compile time from around 20 minutes to less than 2 minutes,

  1. In project build settings, search for "Optimization" Turn Debug to "Fastest[-O3]" or above.
  2. Set Build for Active Architecture: YES
  3. Debug information format: DWARF
  4. Whole Module Optimization: NO

I wasted countless hours waiting for the project to build only to realize I had to make that one little change and had to wait for a whole another 30 minutes to test it. These are the settings that worked for me. (I'm still experimenting with the settings)

But, make sure you at least set "DWARF with dSYM" (if you want to monitor your application) and Build Active Architecture to "NO" for Release/Archiving to push to iTunes Connect (I remember wasting a few hours here too).

查看更多
Explosion°爆炸
6楼-- · 2019-01-03 12:18

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.

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

For Xcode 8, go to project settings, then Editor > Add Build Setting > Add User-Defined Setting, and add the following:

SWIFT_WHOLE_MODULE_OPTIMIZATION = YES

Adding this flag dropped our clean-build compile times from 7 mins to 65s for a 40KLOC swift project, miraculously. Also can confirm 2 friends have seen similar improvements on enterprise projects.

I can only assume this is some kind of bug in Xcode 8.0

EDIT: It doesn't seem to work anymore in Xcode 8.3 for some people.

查看更多
登录 后发表回答