iOSプロジェクトのビルド時間を計測する
#パフォーマンス計測
概要
iOS プロジェクトのビルド時間の計測&改善方法について知見をまとめていきます
計測
swiftc によるコンパイル時間の計測
Swift の diagnostic options
Swift コンパイラのパフォーマンスについての公式ドキュメントとして、下記のようなものがある。
Swift Compiler Performance - apple/swift
この中で、特に時間を計測&出力できるオプションとして、下記が存在する。これらを Swift コンパイラの引数として与えてやると、各々対応した時間の出力が行える。
table:options
オプション 概要
-driver-time-compilation driver が実行した各ジョブにかかった時間を出力する
-Xfrontend -debug-time-function-bodies 関数毎の型推論にかかった時間を出力する
-Xfrontend -debug-time-expression-type-checking 式ごとの型推論にかかった時間を出力する. 関数よりも詳細
https://github.com/apple/swift/blob/main/docs/CompilerPerformance.md#diagnostic-options
関数の型推論にかかった時間を集計する
xcodebuild の OTHER_SWIFT_FLAGS に上記オプションを指定することで、標準出力に結果を出力することができる。特に、関数ごとの型推論については、標準出力に対して出力結果を便利に取り出すような比較的有名なワンライナーがあるので、これを利用すれば機械的に処理できる。
code:console
xcodebuild -workspace App.xcworkspace -scheme App clean build OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-function-bodies" | grep .0-9ms | grep -v ^0.0-9ms | sort -nr > culprits.txt
Profiling your Swift compilation times
fastlane を利用している場合、fastlane gym 等のオプションにこのワンライナーを指定するオプションが既に存在している。analyze_build_time というオプションなので、これを設定すると culprits.txt に集計結果を出力できる。
https://github.com/fastlane/fastlane/issues/7584
https://github.com/fastlane/fastlane/pull/7586
xcprofiler というツールを利用すると、集計結果をより扱いやすい形で得ることができるので、こちらを利用するのも良さそう。
Swiftプロジェクトのビルド時間を計測・改善するxcprofilerを作った話 - クックパッド開発者ブログ
xcodebuild オプションによる追加情報の出力
Build Timing Summary によるビルド時間の内訳
Build Timing Summary を出力すると、どこにどれだけの時間がかかったか?の内訳を出力することができる。例えば、Swift のコンパイル時間や、Storyboard, xib のコンパイル時間などの内訳を確認することができる。
Xcode にて確認する場合、Product > Perform Action > Build With Timing Summary と選択してビルドすると、結果を閲覧できる。xcodebuild の場合は、-showBuildTimingSummary オプションを付与すると出力することができる。ただし、archive コマンドの時にはこれは出力されないので注意。
また、New Build System によって並列ビルドをしている場合、どうやら並列して走ったビルドの総計が反映されるようで、Summary ないの全ての時間を足してもビルド時間と合致はしなかった。
また、出力はシンプルに複数行に分けて項目と時間が並ぶだけで、あまり機械的に優しくなく、パースしづらい。
xcodebuild のビルドログによる計測
ビルドログの出力先
xcodebuild は標準出力の他に、ビルドログからより詳細な情報を得ることができる。ビルドログと呼べるものは、DerivedData 配下の Logs ディレクトリ内に存在している。
Xcode 12 時点だと、ビルド&テストを実行すると、Logs ディレクトリ配下に下記のようにファイルが出力される。Build/LogStoreManifest.plist や Build/xcactivitylog 内に、ビルドに関する情報が格納されている。
code:console
$ tree
.
├── Build
│   ├── 67679CBF-2338-4A3A-B8A7-CDAAB8813D7E.xcactivitylog
│   ├── 98A94D06-6462-4A28-BF44-64E6795D68D6.xcactivitylog
│   ├── BF304821-8648-4AE2-946C-ACE909D23849.xcactivitylog
│   └── LogStoreManifest.plist
├── Package
│   └── LogStoreManifest.plist
└── Test
└── LogStoreManifest.plist
Xcode のビルドログの読込 - クックパッド開発者ブログ
XCLogParser
ファイルを読みつつ自力で解読することもできるが、パースするための OSS がいくつか存在する。中でも人気そうだったのが Spotify の XCLogParser だった。xclogparser は、以下のように homebrew でインストールでき、
code:console
$ brew install xclogparser
以下の機能を持っている。
Dump
xcactivitylog ファイルのほぼそのままの内容を JSON 形式に dump する
Parse
xcactivitylog ファイルの内容を別の形式で出力する
JSON, flat JSON, summary JSON, issues JSON, Chrome Tracer file, HTML page
オプションを追加して得られた追加の情報もパースできる
swiftc の -Xfrontend -debug-time-expression-type-checking や -Xfrontend -debug-time-function-bodies オプションによる出力結果のパース
Other Linker Flags に -Xlinker -print_statistics を追加したことによる ld64 からのリンク時間の出力結果のパース
-ftime-trace オプションを指定した際の、clang によるトレースデータのパース
Manifest
LogStoreManifest.plist ファイルをパースし、JSON 形式で出力する
XCLogParser を試す
試しに、HTML で出力してみる。下記のようなコマンドを実行すると、HTML が出力される。
code:console
$ xclogparser parse --file 67679CBF-2338-4A3A-B8A7-CDAAB8813D7E.xcactivitylog --reporter html --output out
結果は下記。
全体のビルド時間
C/Swift ファイルのビルド時間
Build Target 毎の Build/Compile 時間
遅い Build Target 順のソート結果
遅い C ファイルの上位
遅い Swift ファイルの上位
すごすぎる......
https://gyazo.com/c40731f27dc6c7174430870ab1993cb2
https://gyazo.com/18a5512b2cd8b6499bfe95feafefaad3
json で出力して...
code:bash
# コンパイル時間
$ cat tmp.json | jq '.compilationDuration'
# Target毎のコンパイル時間
$ cat tmp.json | jq '.subSteps[] | { signature: .signature, compilationDuration: .compilationDuration }'
# 型推論が遅いメソッド上位 10 件
$ cat tmp.json | jq '[ .subSteps[].subSteps[].subSteps[] | select(has("swiftFunctionTimes")) | .swiftFunctionTimes[] ] | sort_by(.durationMS) | reverse | .0:10'
# 全体で遅いテスト上位 10 件
$ plutil -convert json -o - metadata.db | jq '[ .Store | to_entries[] | .key as $test | .value[] | to_entries[] | .key as $case | .value | ."test" |= (.+ $test) | ."case" |= (.+ $case) ] | sort_by(.mrnet) | reverse | .0:10'
改善
TODO:
参考
公式の資料
https://github.com/apple/swift/blob/main/docs/CompilerPerformance.md
https://developer.apple.com/videos/play/wwdc2018/415/
https://developer.apple.com/videos/play/wwdc2018/408/
その他
https://developers.cyberagent.co.jp/blog/archives/7815/
https://www.onswiftwings.com/posts/build-time-optimization-part1/
https://www.avanderlee.com/optimization/analysing-build-performance-xcode/
https://scior.hatenablog.com/entry/2019/07/25/232419
https://ricardo-castellanos-herreros.medium.com/speeding-up-xcode-builds-97173cb1adba
https://developers.cyberagent.co.jp/blog/archives/7815/