I’ve been struggling with Swift compilation times recently. Working on a larger app means that we are adding a lot of new Swift code. Swift isn’t the fastest to compile, it sometimes has issues around type inference1 (Swift 3 appears to improve a lot of this, but at time of writing I hadn’t migrated yet).

I had been working previously with function compile times to track down some functions that take far too long to compile (minutes in some cases!). Now that I’ve tackled the low hanging functions in my code base, I wanted to see which files were taking the longest to compile.


Not knowing a massive amount about the Swift compiler, my approach was similar to how I’d measure Objective-C compile times. My first (failed) attempt was trying to parse the “CompileSwift” build steps in the output from xcodebuild and building each file separately (while timing the build process), similar to how OCLint and the compile_commands.json process works. This worked extremely well in a smaller test project, but fell over completely when moving to a larger, real-world project.

I was assuming that the “CompileSwift” output in the xcodebuild.log was similar to the Objective-C “CompileC” output, in that it was a completely self contained description of how to build an individual source file (Hint: It’s not, and building individual Swift sources like this isn’t official supported at all). Xcode and the Swift compiler write out file-lists to temporary files then pass the file path to the “CompileSwift” build step2. This is done to reduce the size of the command that gets passed to the swiftc tool. On my machine, these were being written out to the /var folder: /var/folders/7h/qq7_qn8x6653xq9jlpnddv6w0000gn/T/sources-adc365 and being removed after xcodebuild had finished.

Jordan Rose gave me a great tip about -save-temps, but I couldn’t quite get that to work on the larger real-world project. The https://bugs.swift.org/browse/SR-1788 Swift issue pointed me in the right direction though.

The -debug-time-compilation gave awesome in depth compilation times for each part of the compilation process. Example:

===-------------------------------------------------------------------------===
                               Swift compilation
===-------------------------------------------------------------------------===
  Total Execution Time: 0.0080 seconds (0.0062 wall clock)

   ---User Time---   --User+System--   ---Wall Time---  --- Name ---
   0.0040 ( 50.0%)   0.0040 ( 50.0%)   0.0030 ( 49.0%)  LLVM output
   0.0040 ( 50.0%)   0.0040 ( 50.0%)   0.0010 ( 17.0%)  SILGen
   0.0000 (  0.0%)   0.0000 (  0.0%)   0.0008 ( 13.4%)  Type checking / Semantic analysis
   0.0000 (  0.0%)   0.0000 (  0.0%)   0.0006 (  9.1%)  IRGen
   0.0000 (  0.0%)   0.0000 (  0.0%)   0.0003 (  5.5%)  LLVM optimization
   0.0000 (  0.0%)   0.0000 (  0.0%)   0.0002 (  2.7%)  AST verification
   0.0000 (  0.0%)   0.0000 (  0.0%)   0.0001 (  1.2%)  SIL verification (pre-optimization)
   0.0000 (  0.0%)   0.0000 (  0.0%)   0.0001 (  1.1%)  Parsing
   0.0000 (  0.0%)   0.0000 (  0.0%)   0.0000 (  0.6%)  SIL verification (post-optimization)
   0.0000 (  0.0%)   0.0000 (  0.0%)   0.0000 (  0.5%)  SIL optimization
   0.0000 (  0.0%)   0.0000 (  0.0%)   0.0000 (  0.0%)  Name binding
   0.0080 (100.0%)   0.0080 (100.0%)   0.0062 (100.0%)  Total

With that knowledge, I compiled my app like this:

set -o pipefail && \
    xcodebuild -workspace MyApp.xcworkspace \
    -scheme MyApp \
    -sdk iphonesimulator \
    SYMROOT="${PWD}/build" \
    ONLY_ACTIVE_ARCH=YES \
    OTHER_SWIFT_FLAGS="-D DEBUG -Xfrontend -debug-time-compilation" \
    -destination 'platform=iOS Simulator,name=iPhone 6s' clean build \
    | tee xcodebuild.log | xcpretty

Then parsed the xcodebuild.log file for the results:

cat xcodebuild.log | ruby process_xcodebuild_log.rb > swift_build_times.txt

Here’s the gist for process_xcodebuild_log.rb

and got results like this:

0.1925s /Users/dbeard/Dev/swift_file_compile_times/a.swift
0.5183s /Users/dbeard/Dev/swift_file_compile_times/b.swift
0.6008s /Users/dbeard/Dev/swift_file_compile_times/c.swift
0.1964s /Users/dbeard/Dev/swift_file_compile_times/d.swift
0.1919s /Users/dbeard/Dev/swift_file_compile_times/e.swift
0.7563s /Users/dbeard/Dev/swift_file_compile_times/f.swift

TL;DR, give me a solution

Sorted swift file compilation times:

  1. Download process_xcodebuild_log.rb
  2. Run this:
    set -o pipefail && \
     xcodebuild -workspace MyApp.xcworkspace \
     -scheme MyApp \
     -sdk iphonesimulator \
     SYMROOT="${PWD}/build" \
     ONLY_ACTIVE_ARCH=YES \
     OTHER_SWIFT_FLAGS="-D DEBUG -Xfrontend -debug-time-compilation" \
     -destination 'platform=iOS Simulator,name=iPhone 6s' clean build \
     | tee xcodebuild.log | ruby process_xcodebuild_log.rb | sort -nr > sorted_swift_compile_times.txt
    

This is a temporary solution until the https://bugs.swift.org/browse/SR-1788 issue is resolved in Swift, and I don’t think that’s going to take too long :)