Xcode 编译疾如风-1.如何排查编译耗时问题
开发 iOS 的小伙伴都知道,随着项目的不断庞大,Xcode 项目的编译时间也会越来越长。如果不加管控,会严重影响到我们的开发效率。
全量编译下,我去,30 分钟过去了,拉个屎还能抽根烟...
这不,小菜有些受不了了。开始研究 iOS 编译时长问题。
切入这个编译耗时问题,首先我们要分析编译的耗时在哪里,然后再针对性的进行优化。否则就像无头苍蝇一样乱飞乱撞,事倍功半。
关于这个系列文章有几点说明:1)这个系列会涉及到 Cocoapods,小菜的项目使用该工具进行组件管理。2)标题中提到的编译会涉及到全量编译和增量编译。
我们如何衡量构建时间呢?或者我们有哪些工具能够帮助我们分析构建时间?
ShowBuildOperationDuration
我们暂时先不看pod install
或者pod update
的组件安装耗时,单纯看 Xcode 的 build 耗时情况。如何得知 Xcode build 项目的时长?
一句命令行搞定:
代码语言:javascript复制defaults write com.apple.dt.Xcode ShowBuildOperationDuration YES
终端执行完毕后,我们在使用 Xcode 编译时,便会在 Xcode 的状态条上显示编译时长
有读者朋友问了,如果我不用 Xcode 软件编译呢,比如我使用xcodebuild
来编译,怎么获取到编译耗时呢?
time xcodebuild
很简单,shell 的基本操作,使用time
便可获取执行的时间情况。
Build With Timing Summary
构建时长概要
同样的,如果我们用xcodebuild
也可以获取构建时长概要:
xcodebuild -showBuildTimingSummary
XCLogParser
XCLogParser[1] 是一个命令行工具,用于分析 xcactivitylog 日志文件(注:xcactivitylog 是 Xcode 和 xcodebuild 在构建时保存的一种日志文件)
XCLogParser 可以提供项目中每个模块和文件的构建时间,警告,错误和单元测试结果。
我们在项目编译后,执行脚本:
代码语言:javascript复制xclogparser parse --project Kickstarter --reporter html
便可以在build/xclogparser/reports/时间戳
目录下看到输出了大量的html,直接点击里面的index.html
便可在浏览器中查看具体的编译信息。
我们可以将脚本执行在自动构建 CI 流程中,打完包后可以查看 CI 机器下生成的编译信息。CI 机器起一个静态服务即可。
Swift 代码编译耗时分析
如果项目中存在大量的 Swift 代码,且 Swift 的编译耗时成为了瓶颈,我们可以对 Swift 代码的编译耗时情况进行诊断。
类型检查警告
我们可以在Other Swift Flags
配置检查警告项:
-Xfrontend -warn-long-function-bodies=100
-Xfrontend -warn-long-expression-type-checking=100
然后 Xcode 编译结束后,我们便可以在编译日志中看到函数/表达时编译耗时超过 100毫秒 的警告,点击这些警告便可以进入具体的代码位置,从而帮助我们优化代码。
编译器诊断选项
在 Swift 编译器性能[2]中,Apple 官方提到了几个诊断选项:
- -driver-time-compilation
- -Xfrontend -debug-time-function-bodies
- -Xfrontend -debug-time-expression-type-checking
- -Xfrontend -print-stats
- -Xfrontend -print-clang-stats
- -Xfrontend -print-stats -Xfrontend -print-inst-counts
我们重点关注-debug-time-function-bodies
、-debug-time-expression-type-checking
。
-debug-time-function-bodies
可以统计打印出 Swift 文件中函数体编译耗时:
9.16ms test.swift:15:6 func find<R>(_ range: R, value: R.Element) -> R where R : IteratorProtocol, R.Element : Eq
0.28ms test.swift:27:6 func findIf<R>(_ range: R, predicate: (R.Element) -> Bool) -> R where R : IteratorProtocol
2.81ms test.swift:40:6 func count<R>(_ range: R, value: R.Element) -> Int where R : IteratorProtocol, R.Element : Eq
0.64ms test.swift:51:6 func countIf<R>(_ range: R, predicate: (R.Element) -> Bool) -> Int where R : IteratorProtocol
...
例如
代码语言:javascript复制xcodebuild -project 'Kickstarter.xcodeproj'
-scheme 'Kickstarter-iOS'
-configuration 'Debug'
-sdk 'iphonesimulator'
clean build
OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-compilation" |
awk '/CompileSwift normal/,/Swift compilation/{print; getline; print; getline; print}' |
grep -Eo "^CompileSwift. .swift|d .d seconds" |
sed -e 'N;s/(.*)n(.*)/2 1/' |
sed -e "s|CompileSwift normal x86_64 $(pwd)/||" |
sort -rn |
head -3
25.6026 seconds Library/ViewModels/SettingsNewslettersCellViewModel.swift
24.4429 seconds Library/ViewModels/PledgeSummaryViewModel.swift
24.4312 seconds Library/ViewModels/PaymentMethodsViewModel.swift
而-debug-time-expression-type-checking
更细致,可以打印出表达式的编译耗时:
0.20ms test.swift:17:16
1.82ms test.swift:18:12
6.35ms test.swift:19:8
0.11ms test.swift:22:5
0.02ms test.swift:24:10
0.02ms test.swift:30:16
...
例如
代码语言:javascript复制xcodebuild -project 'Kickstarter.xcodeproj'
-scheme 'Kickstarter-iOS'
-configuration 'Debug'
-sdk 'iphonesimulator'
clean build
OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-expression-type-checking
-Xfrontend -debug-time-function-bodies" |
grep -o "^d*.d*mst[^$]*$" |
awk '!visited[$0] ' |
sed -e "s|$(pwd)/||" |
sort -rn |
head -5
16226.04ms Library/Styles/UpdateDraftStyles.swift:31:3
10551.24ms Kickstarter-iOS/Views/RewardCardContainerView.swift:171:16 instance method configureBaseGradientView()
10547.41ms Kickstarter-iOS/Views/RewardCardContainerView.swift:172:7
8639.30ms Kickstarter-iOS/Views/Controllers/AddNewCardViewController.swift:396:67
8233.27ms KsApi/models/templates/ProjectTemplates.swift:94:5
后续小菜还会输出 Swift 编译耗时优化的文章,敬请期待。
BuildTimeAnalyzer
BuildTimeAnalyzer[3] 是一款开源工具,使用很简单。其本质还是利用 Swift 编译器的诊断选项将耗时部分输出出来。
更多阅读
- Xcode Build Time Optimization 1[4]
- Xcode Build Time Optimization 2[5]
参考资料
[1]
XCLogParser: https://github.com/spotify/XCLogParser
[2]
Swift 编译器性能: https://github.com/apple/swift/blob/main/docs/CompilerPerformance.md#diagnostic-options
[3]
BuildTimeAnalyzer: https://github.com/RobertGummesson/BuildTimeAnalyzer-for-Xcode
[4]
Xcode Build Time Optimization 1: https://www.onswiftwings.com/posts/build-time-optimization-part1/
[5]
Xcode Build Time Optimization 2: https://www.onswiftwings.com/posts/build-time-optimization-part2/