Xcode编译疾如风系列-1.分析编译耗时

2021-04-23 14:50:32 浏览数 (1)

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来编译,怎么获取到编译耗时呢?

代码语言:javascript复制
time xcodebuild

很简单,shell 的基本操作,使用time便可获取执行的时间情况。

Build With Timing Summary

构建时长概要

同样的,如果我们用xcodebuild也可以获取构建时长概要:

代码语言:javascript复制
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配置检查警告项:

代码语言:javascript复制
-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 文件中函数体编译耗时:

代码语言:javascript复制
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更细致,可以打印出表达式的编译耗时:

代码语言:javascript复制
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/

0 人点赞