Slow Swift Compiler Performance

I went down a rabbit hole looking at Swift compiler performance this week. Since most of the diagnostic flags are unsupported they are not well documented. For future reference here’s my summary of what’s available.

Starting With A Puzzle

This tweet by Nick Lockwood is what triggered my curiosity. In brief, the Swift compiler is much slower handling this first version of a boolean test:

if flag == false {
  // do something
}

Rewriting the comparison to false with the negation operator is significantly faster to compile:

if !flag {
  // do something
}

At first glance, that’s surprising. There’s some explanation in this Swift Forums post. The bigger question for me was how do you find out which is faster?

I should make it clear that we’re talking about compiler performance and not runtime performance. The Swift compiler may be slow to build your project but that doesn’t mean it will be slow to run. You can also have code that is quick to compile that has terrible runtime performance. If you’re investigating slow runtime performance you can use Instruments. What do you do if you want to understand why the compiler is slow?

Build Timing

Before you jump to conclusions and blame the Swift compiler for slow build times it’s worth getting a summary of the whole build process. Xcode 10 added a build timing summary.

From the Xcode menu: Product > Perform Action > Build With Timing Summary:

Xcode perform action menu with Build With Timing Summary selected

You’ll find the summary at the end of the build log in the report navigator (select Recent to see the results of the last build):

Build Timing Summary log

Compiling your Swift source code is one of many steps that Xcode performs when building your project. Compiling assets catalogs, SceneKit, slow build scripts, etc. can all contribute to a slow build.

In this case my project is compiling in under 20 seconds so I don’t have much to worry about, but we can dig deeper if needed.

Swift Compiler - Custom Flags

There are a couple of compiler flags you can enable to get some extra information when the compiler is slowing down:

  • -Xfrontend -warn-long-function-bodies=<limit>
  • -Xfrontend -warn-long-expression-type-checking=<limit>

Apple mentions the long expression type checking warning in the Xcode 9 release notes but otherwise they don’t document or support these flags.

Both flags take a value for the number of milliseconds above which the compiler will show a warning. Add the flags in the build settings for a target in the Swift Compiler - Custom Flags section using the Other Swift Flags setting:

Xcode Build Settings - Other Swift Flags

You may need to experiment with the threshold value. I find I get a lot of warnings between 100ms and 200ms:

-Xfrontend flags using 200ms

Coming back to the original puzzle. Here’s an example of the warning when comparing a boolean (I’m using Xcode 12, Swift 5.3):

Instance method took 124ms to type-check

Rewriting that example removes the warning:

if !authorized {
  enableRed()
}

What Do You Need To Do?

Maybe nothing. I like the idea of running the Build Timing Summary before/after upgrading Xcode to see what impact new versions of the build system and the Swift compiler have. There’s a good WWDC 2018 Session on speeding up your Xcode builds.

If you have concerns about slow Swift compilation times the debug flags can give you some hints where to look. The general advice is to split up complex expressions and add explicit type annotations but beyond that it’s not always obvious how you can rewrite something.

Read More