Swift編譯慢-優(yōu)化 - 優(yōu)化編譯速度

找出編譯耗時(shí)過(guò)長(zhǎng)的文件

要優(yōu)化項(xiàng)目的編譯速度,首先需要把耗時(shí)過(guò)長(zhǎng)的文件找出來(lái),然后進(jìn)行重點(diǎn)優(yōu)化。這里會(huì)用到Xcode build的兩個(gè)OTHER_SWIFT_FLAGS

  • -Xfrontend: 如果編譯或類型檢查時(shí)耗時(shí)多長(zhǎng),則在Xcode中輸出警告。
  • -debug-time-function-bodies:輸出每個(gè)函數(shù)的編譯時(shí)長(zhǎng)。

添加這些flag的方法為:

  1. 選中Target
  2. 選中Build Settings
  3. 搜索“Other Swift Flags”
  4. 添加”-Xfrontend -debug-time-function-bodies“
image

基于這兩個(gè)flag,有3個(gè)方法可以找到耗時(shí)過(guò)長(zhǎng)的文件,這三個(gè)方法的本質(zhì)是一樣,只不過(guò)是手動(dòng)操作的多少而已,推薦使用第一種。

1.(推薦)使用BuildTimeAnalyzer

BuildTimeAnalyzer是一個(gè)macOS app,用于輔助分析Xcode的編譯log。

github下載這個(gè)app的源碼后,在Xcode打開(kāi)項(xiàng)目,點(diǎn)擊Product >> Archive >> Export,就可以生成BuildTimeAnalyzer。雙擊運(yùn)行看到如下的頁(yè)面,

image

按照使用說(shuō)明一步步操作之后,就可以看到類似于下圖的界面,點(diǎn)擊任意一行,Xcode就會(huì)跳轉(zhuǎn)到對(duì)應(yīng)的函數(shù)中。

image

2. 命令行方式

命令行方式不需要修改Xcode的項(xiàng)目設(shè)置,將下面命令行中的SwiftCompileTime替換為你的項(xiàng)目名,在項(xiàng)目所在目錄下直接運(yùn)行即可,它會(huì)將每個(gè)文件的編譯時(shí)間都輸出到culprits.txt中。這個(gè)命令執(zhí)行的時(shí)間可能會(huì)非常長(zhǎng)!一定要耐心等待。

項(xiàng)目后綴名為.xcworkspace時(shí)使用:
xcodebuild -workspace SwiftCompileTime.xcworkspace -scheme SwiftCompileTime clean build OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-function-bodies" | grep .[0-9]ms | grep -v ^0.[0-9]ms | sort -nr > culprits.txt

項(xiàng)目后綴名為.xcodeproj時(shí)使用:
xcodebuild -project SwiftCompileTime.xcodeproj -scheme SwiftCompileTime clean build OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-function-bodies" | grep .[0-9]ms | grep -v ^0.[0-9]ms | sort -nr > culprits.txt

輸出文件的內(nèi)容的樣式如下:

210.61ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/String+TVGuor.swift:34:34   get {}
196.79ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/String+TVGuor.swift:34:34   get {}
128.94ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/Optional+Extension.swift:23:28  get {}
123.50ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/Optional+Extension.swift:23:28  get {}
119.40ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/VideoAlbum.swift:565:17 @objc public dynamic func formatVideoCount(by type: TVGVideoAlubmType) -> String
117.80ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/String+Extension.swift:109:23   get {}
114.67ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/NSObject+Extension.swift:13:32  get {}
112.09ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/String+Extension.swift:109:23   get {}
110.23ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/NSObject+Extension.swift:13:32  get {}
108.80ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/Constants+Enum.swift:61:17  static func channelNo(fromChannelString channel: String?) -> Int?
103.16ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/String+Extension.swift:26:10    func urlQueryEncoded() -> String
102.63ms    /Users/xxx/Documents/Workspace/SwiftCompileTime/SwiftCompileTime/TVGuor/Constants+Enum.swift:61:17  static func channelNo(fromChannelString channel: String?) -> 

3.(不推薦)手動(dòng)查看Xcode log方式

這個(gè)方式需要大量的手動(dòng)操作,效率很低,不推薦,列出來(lái)僅做參考。步驟如下:

  • 按照上面的教程,在Xcode中添加”-Xfrontend -debug-time-function-bodies“的flags
  • 編譯
  • 點(diǎn)擊?-9(Xcode 8上是?-8)跳轉(zhuǎn)到Build Report
  • 右擊build log >> Expand All Transcripts 就可以看到編譯時(shí)長(zhǎng)的log了


    image
image

通過(guò)修改項(xiàng)目配置實(shí)現(xiàn)優(yōu)化

優(yōu)先級(jí):高

在修改代碼之前,可以先通過(guò)修改Xcode的配置實(shí)現(xiàn)編譯速度的優(yōu)化

  • Optimization Level。
    • Build Settings -> Optimization Level -> Debug模式下設(shè)置為None。
    • Build Settings -> User-Defined -> 添加 SWIFT_WHOLE_MODULE_OPTIMIZATION = YES


      image
  • Debug模式下不生成dSYM文件。
    • dSYM(debug symbols file)是一個(gè)存儲(chǔ)debug信息的文件,每次編譯時(shí)都會(huì)生成。但在Debu模式下我們并不需要它,所以可以將Debug的值設(shè)為"DWARF",僅在Release使用"DWARF with dSYM File".
image

避免使用簡(jiǎn)潔表達(dá)式

這是一個(gè)讓人很糾結(jié)的優(yōu)化。Swift提供了很多簡(jiǎn)潔的表達(dá)方式,比如定義變量時(shí)可以不用聲明變量的類型,使用??為optional提供默認(rèn)值等,這些功能為Swift開(kāi)發(fā)者提供了不少便利,也構(gòu)成了Swift簡(jiǎn)潔的編程風(fēng)格。不過(guò)它們?cè)谔峁┍憷耐瑫r(shí),也增加了不少編譯的時(shí)間。如何抉擇,是令開(kāi)發(fā)者頭疼的一個(gè)問(wèn)題。我個(gè)人的觀點(diǎn)是,對(duì)于那些并沒(méi)有提供太多便利的表達(dá)式,一定要替換為編譯更快的寫法;其他簡(jiǎn)潔表達(dá)式,只要沒(méi)有顯著的增加編譯時(shí)長(zhǎng),繼續(xù)使用。

一定要避免使用String1 + String2

優(yōu)先級(jí):高

一定要避免使用加好將兩個(gè)字符串合并起來(lái),可以改用"\(string1)\(string2)"

"123" + string1 + "456"

替換為

"123\(string1)456"

一定要避免使用Array1 + Array2

優(yōu)先級(jí):高

一定要避免使用加好將兩個(gè)數(shù)組合并起來(lái),可以改用array1.append(contentsOf: array2)。

array1 + array2

替換為

array1.append(contentsOf: array2)

對(duì)于復(fù)雜變量的定義,添加類型聲明

優(yōu)先級(jí):高

在Swift中定義變量時(shí)可以不帶類型聲明,編譯器會(huì)根據(jù)初始化的值去猜測(cè)變量類型,比如let str = "123"就定義了一個(gè)String。這為Swift開(kāi)發(fā)者提供了很大的便利,但編譯器分析變量類型是需要時(shí)間的。從減少編譯時(shí)間的角度考慮,肯定是要給每個(gè)變量定義都加上類型聲明,但這樣就完全不是在寫Swift的代碼了!

其實(shí)對(duì)于簡(jiǎn)單的變量定義,比如let str = "123"let str: String = "123",添不添加類型聲明,編譯時(shí)間都差不多,但對(duì)于下面兩個(gè)復(fù)雜的類型,最好還是加上type。

let myCompany = [
   "employees": [
        "employee 1": ["attribute": "value"],
        "employee 2": ["attribute": "value"],
        "employee 3": ["attribute": "value"],
        "employee 4": ["attribute": "value"],
        "employee 5": ["attribute": "value"],
        "employee 6": ["attribute": "value"],
        "employee 7": ["attribute": "value"],
        "employee 8": ["attribute": "value"],
        "employee 9": ["attribute": "value"],
        "employee 10": ["attribute": "value"],
        "employee 11": ["attribute": "value"],
        "employee 12": ["attribute": "value"],
        "employee 13": ["attribute": "value"],
        "employee 14": ["attribute": "value"],
        "employee 15": ["attribute": "value"],
        "employee 16": ["attribute": "value"],
        "employee 17": ["attribute": "value"],
        "employee 18": ["attribute": "value"],
        "employee 19": ["attribute": "value"],
        "employee 20": ["attribute": "value"],
    ]
]

// build time: 330ms
let tm1 = ceil(abs(PlayerWaitingStartInterval - timer.fireDate.timeIntervalSinceNow) * 1000)

// build time: 79ms
// Add type annotation
let tm1: Double = ceil(abs(PlayerWaitingStartInterval - timer.fireDate.timeIntervalSinceNow) * 1000)

// build time: 26ms
// Add type annotation & separate into two parts
let interval: Double = abs(PlayerWaitingStartInterval - timer.fireDate.timeIntervalSinceNow)
let tm1: Double = ceil(interval * 1000)

避免使用Nil-Coalescing operator ??

優(yōu)先級(jí):低

??同樣會(huì)增加編譯時(shí)長(zhǎng)。但我覺(jué)得這個(gè)優(yōu)化的優(yōu)先級(jí)并不高,僅在編譯時(shí)長(zhǎng)真的非常長(zhǎng)時(shí)才考慮使用。

let name = string ?? ""

替換為

if let name = string { 
 /* string has value */
} else {
 /* string is nil*/
}

避免使用條件運(yùn)算符?:

優(yōu)先級(jí):低

?:??一樣會(huì)增加編譯時(shí)長(zhǎng),但優(yōu)化的優(yōu)先級(jí)也不高。

let letter = isFirst ? "a" : "b"

替換為

var letter = ""
if isFirst { 
  letter = "a"
} else {
  letter = "b"
}

預(yù)計(jì)算

優(yōu)先級(jí):中

不要直接在if-else的condition中做計(jì)算,會(huì)大大的增加編譯時(shí)長(zhǎng)。可以先在外部創(chuàng)建一個(gè)變量保存計(jì)算好的值,再將這個(gè)變量作為condition。

if number == 60 * 60 {}

替換為

let number: Double = 60 * 60
if number == 3600 {
}

Closures and lazy properties

優(yōu)先級(jí):高

Lazy Property和Closures也可能導(dǎo)致編譯時(shí)長(zhǎng)的增加。下面這個(gè)代碼是一個(gè)很普通的lazy Property定義,但它卻有可能導(dǎo)致過(guò)長(zhǎng)的編譯時(shí)間。

// Lazy property
private(set) lazy var chartViewColors: [UIColor] = [
    self.chartColor,
    UIColor(red: 86/255, green: 84/255, blue: 124/255, alpha: 1),
    UIColor(red: 80/255, green: 88/255, blue: 92/255, alpha: 1),
    UIColor(red: 126/255, green: 191/255, blue: 189/255, alpha: 1),
    UIColor(red: 161/255, green: 77/255, blue: 63/255, alpha: 1),
    UIColor(red: 235/255, green: 185/255, blue: 120/255, alpha: 1),
    UIColor(red: 100/255, green: 126/255, blue: 159/255, alpha: 1),
    UIColor(red: 160/255, green: 209/255, blue: 109/255, alpha: 1),
    self.backgroundGradientView.upperColor
]

如果將它改為下面的closure后,編譯時(shí)間會(huì)更長(zhǎng)。

private let createChartViewColors = { () -> [UIColor] in
    let colors = [
        UIColor(red: 86/255, green: 84/255, blue: 124/255, alpha: 1),
        UIColor(red: 80/255, green: 88/255, blue: 92/255, alpha: 1),
        UIColor(red: 126/255, green: 191/255, blue: 189/255, alpha: 1),
        UIColor(red: 161/255, green: 77/255, blue: 63/255, alpha: 1),
        UIColor(red: 235/255, green: 185/255, blue: 120/255, alpha: 1),
        UIColor(red: 100/255, green: 126/255, blue: 159/255, alpha: 1),
        UIColor(red: 160/255, green: 209/255, blue: 109/255, alpha: 1),
    ]
    return colors
}

解決方案是將初始化代碼移到一個(gè)單獨(dú)的函數(shù)中。從代碼風(fēng)格的角度考慮,將過(guò)長(zhǎng)的初始化代碼移到單獨(dú)的函數(shù)中也是一個(gè)好習(xí)慣,查看代碼時(shí)可以更加清晰地看出一個(gè)class定義了哪些property。

// Cumulative build time: 56.3ms
private(set) lazy var chartViewColors: [UIColor] = self.createChartViewColors()

// Build time: 6.2ms
private func createChartViewColors() -> [UIColor] {
    return [
        chartColor,
        UIColor(red: 86/255, green: 84/255, blue: 124/255, alpha: 1),
        UIColor(red: 80/255, green: 88/255, blue: 92/255, alpha: 1),
        UIColor(red: 126/255, green: 191/255, blue: 189/255, alpha: 1),
        UIColor(red: 161/255, green: 77/255, blue: 63/255, alpha: 1),
        UIColor(red: 235/255, green: 185/255, blue: 120/255, alpha: 1),
        UIColor(red: 100/255, green: 126/255, blue: 159/255, alpha: 1),
        UIColor(red: 160/255, green: 209/255, blue: 109/255, alpha: 1),
        backgroundGradientView.upperColor
    ]
}

奇怪的優(yōu)化

這里列出的是Swift編譯優(yōu)化中的一些奇怪現(xiàn)象,感覺(jué)更像是編譯器的一些bug,不必花太多時(shí)間在這方便。

CGFloat轉(zhuǎn)化為CGFloat

優(yōu)先級(jí):低

下面的代碼,將一個(gè)值為CGFloat的轉(zhuǎn)化為CGFloat時(shí)反而會(huì)導(dǎo)致編譯時(shí)長(zhǎng)的大大增加。這是一個(gè)很奇怪的現(xiàn)象,估計(jì)是編譯器的bug。

// Build time: 3431.7ms
return CGFloat(M_PI) * (CGFloat((hour + hourDelta + CGFloat(minute + minuteDelta) / 60) * 5) - 15) * unit / 180

// Build time: 3.0ms
return CGFloat(M_PI) * ((hour + hourDelta + (minute + minuteDelta) / 60) * 5 - 15) * unit / 180

Round()

優(yōu)先級(jí):低

下面的代碼只是調(diào)用了round()就增加了97%的編譯時(shí)長(zhǎng),很奇怪。

// Build time: 1433.7ms
let expansion = a — b — c + round(d * 0.66) + e
// Build time: 34.7ms
let expansion = a — b — c + d * 0.66 + e

References

Profiling your Swift compilation times

Uber: Swift with a hundred engineers

Speed up Swift compile time

Regarding Swift build time optimizations

Swift build time optimizations?—?Part 2

Improving Swift Compilation Times from 12 to 2 Minutes

作者:mobilefellow
鏈接:http://www.itdecent.cn/p/89e9df02e244
來(lái)源:簡(jiǎn)書
簡(jiǎn)書著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請(qǐng)聯(lián)系作者獲得授權(quán)并注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 找出編譯耗時(shí)過(guò)長(zhǎng)的文件 要優(yōu)化項(xiàng)目的編譯速度,首先需要把耗時(shí)過(guò)長(zhǎng)的文件找出來(lái),然后進(jìn)行重點(diǎn)優(yōu)化。這里會(huì)用到Xcod...
    mobilefellow閱讀 2,411評(píng)論 2 8
  • 1、通過(guò)CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫(kù)組件 SD...
    陽(yáng)明AI閱讀 16,170評(píng)論 3 119
  • 今天穿上了旗袍加紅色的外披,希望大寶能夠旗開(kāi)得勝,鴻運(yùn)當(dāng)頭。我們?cè)诩依镬牡戎荚嚨南ⅰH思乙豢纪暝嚵ⅠR就...
    番茄媽閱讀 136評(píng)論 0 0
  • 你為什么活得那么累? 當(dāng)這個(gè)問(wèn)題拋向你的時(shí)候,不知道你的第一反應(yīng)是什么,我腦子里晃過(guò)的是三個(gè)字——不甘心。 我們常...
    紫JZZ閱讀 391評(píng)論 1 4
  • 簡(jiǎn)介: 他霸道腹黑,邪笑著,修長(zhǎng)的手指輕易地勾起她的下巴:“薰衣,別忘了小時(shí)候的約定,你,必須嫁給我?!保凰麥厝崴?..
    瓶九玖閱讀 237評(píng)論 1 1

友情鏈接更多精彩內(nèi)容