[翻譯]關於Swift的編譯時間優化

原文連接: Regarding Swift build time optimizations

上週,在我讀完 @nickoneill 寫的一篇優秀的博文《 爲緩慢的Swift編譯時間提速》後,我發現用一個不一樣的角度去審視 Swift 代碼並非很難的一件事。

能夠被認爲是簡潔的一行代碼如今引起了一個新的問題 -- 是否應該把這行代碼重構成對應的9行代碼以讓編譯器更容易工做(看看接下來要講的關於空合運算符(nil coalescing operator)的示例)?到底哪一個纔是更重要的,簡潔的代碼仍是對編譯器友好的代碼?這取決於項目的大小和開發者的想法。

慢着。。。這裏有一個 Xcode 插件

在展現具體的例子以前,我先想到就是手動查看日誌是一件很是耗時的事情。有人提出了用終端命令可讓這件事情變得比較容易,可是我更進一步,把這個用 Xcode 插件 給實現出來了。


對我來講,最初的目的就是找到並修復最耗時的地方,但我如今的想法是讓它經歷更多的迭代過程。這樣的話我就不只可讓代碼編譯更有效率,還能夠防止第一次進入項目的耗時。

更加驚喜的是

我常常在多個 Git 分支間跳來跳去,等待一個緩慢的項目編譯完成每每浪費了大量的時間。我想了好長一段時間爲何個人一個寵物項目會編譯地這麼緩慢(大概2萬行 Swift 代碼)。

在我學習了到底是緣由致使的這件事以後,我不得不認可個人確很吃驚,一行代碼就須要幾秒鐘來編譯。

讓咱們一塊兒看看幾個例子。

空合操做符

編譯器是很不喜歡這裏的第一種方式的。在展開下面兩處簡寫的代碼以後,編譯時間減小了99.4%。

// 編譯時間: 5238.3ms
return CGSize(width: size.width + (rightView?.bounds.width ?? 0) + (leftView?.bounds.width ?? 0) + 22, height: bounds.height)

// 編譯時間: 32.4ms
var padding: CGFloat = 22
if let rightView = rightView {
    padding += rightView.bounds.width
}

if let leftView = leftView {
    padding += leftView.bounds.width
}
return CGSizeMake(size.width + padding, bounds.height)
複製代碼


ArrayOfStuff + [Stuff]

這個看起來像下面這樣:

return ArrayOfStuff + [Stuff]
// 而不是
ArrayOfStuff.append(stuff)
return ArrayOfStuff複製代碼
我常常這樣作,每次都會對所需的編譯時間產生影響。下面是最差的一個,這裏的編譯時間減小了97.9%。

// 編譯時間: 1250.3ms
let systemOptions = [ 7, 14, 30, -1 ]
let systemNames = (0...2).map{ String(format: localizedFormat, systemOptions[$0]) } + [NSLocalizedString("everything", comment: "")]
// 一些中間的代碼
labelNames = Array(systemNames[0..<count]) + [systemNames.last!]

// 編譯時間: 25.5ms
let systemOptions = [ 7, 14, 30, -1 ]
var systemNames = systemOptions.dropLast().map{ String(format: localizedFormat, $0) }
systemNames.append(NSLocalizedString("everything", comment: ""))
// 一些中間的代碼
labelNames = Array(systemNames[0..<count])
labelNames.append(systemNames.last!)
複製代碼


三元運算符

僅僅只是把三元運算符替換成 if-else 語句,就讓編譯時間減小了92.9%。若是將 map 換成 for 循環,就又能減小75%(可是那樣的話個人眼睛可就受不了了)。😉

// 編譯時間: 239.0ms
let labelNames = type == 0 ? (1...5).map{type0ToString($0)} : (0...2).map{type1ToString($0)}

// 編譯時間: 16.9ms
var labelNames: [String]
if type == 0 {
    labelNames = (1...5).map{type0ToString($0)}
} else {
    labelNames = (0...2).map{type1ToString($0)}
}複製代碼

轉換 CGFloat 到 CGFloat

沒聽懂我在說什麼?其實下面例子中值已是 CGFloat 了,而且有些括號是多餘的。在清理完這些冗餘以後,編譯時間減小了99.9%。

// 編譯時間: 3431.7 ms
return CGFloat(M_PI) * (CGFloat((hour + hourDelta + CGFloat(minute + minuteDelta) / 60) * 5) - 15) * unit / 180

// 編譯時間: 3.0ms
return CGFloat(M_PI) * ((hour + hourDelta + (minute + minuteDelta) / 60) * 5 - 15) * unit / 180
複製代碼


Round()

下面是一個很奇怪的例子,下面的例子中變量是一個局部變量與實例變量的混合。這個問題彷佛不是出在四捨五入自己,而是在於結合代碼的方法。去掉四捨五入的方法大概能減小 **97.6%** 的構建時間。

// 編譯時間: 1433.7ms
let expansion = a — b — c + round(d * 0.66) + e
// 編譯時間: 34.7ms
let expansion = a — b — c + d * 0.66 + e
複製代碼


注意:以上全部測試都在MacBool Air(13英寸,2013年中)上進行。

嘗試一下吧

無論你是否面臨過編譯時間太長的問題,編寫對編譯器友好的代碼都是很是有用的。我確信你會在其中找到一些驚喜。做爲參考,這裏有完整的代碼,個人工程中能夠5秒內完成編譯…

import UIKit

class CMExpandingTextField: UITextField {

func textFieldEditingChanged() {
    invalidateIntrinsicContentSize()
}

override func intrinsicContentSize() -> CGSize {
    if isFirstResponder(), let text = text {
        let size = text.sizeWithAttributes(typingAttributes)
          return CGSize(width: size.width + (rightView?.bounds.width ?? 0) + (leftView?.bounds.width ?? 0) + 22, height: bounds.height)
        }
        return super.intrinsicContentSize()
    }
}
複製代碼
相關文章
相關標籤/搜索