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 already 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 | xcprettyThen parsed the xcodebuild.log file for the
results:
cat xcodebuild.log | ruby process_xcodebuild_log.rb > swift_build_times.txtHere'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:
- Download process_xcodebuild_log.rb
- 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 :)