天天看點

[譯] 優化 Swift 的編譯時間優化 Swift 的編譯時間

<b>本文講的是[譯] 優化 Swift 的編譯時間,</b>

<b></b>

在 Swift 所有的特性中,有一件事有時會相當惱人,那就是在用 Swift 編寫更大規模的項目時,它一般會編譯多久。盡管 Swift 編譯器在保證運作時安全方面做的更多,但是它的編譯時間要比 Objective-C 編譯時間長很多。(是以)我想研究一下,是否我們可以幫助編譯器讓他工作的更快。

現在,在我們開始之前,我隻想說我不想這篇文章以任何形式的方式來批判 Swift 或它的團隊工作。我知道 Swift 編譯器的開發者,包含 Apple 公司和開源社群,都在持續地對編譯器速度、功能和穩定性做出重大改進。希望這篇博文能随着時間的流逝而顯得多餘,但在那之前,我隻是想提供一些我發現可以提升編譯速度的實用技巧。

在開始優化工作之前,建立一個能衡量你改進的基準總是好的。我是通過在 Xcode 裡,給應用的 target 添加兩個簡單的腳本作為運作腳本階段來實作的。

在編譯源檔案之前,添加下面的腳本:

在最後,添加這個腳本:

現在,這個腳本隻會測算編譯器編譯應用自己的源檔案的時間(為了測量出整個引用的編譯時間,你可以使用 Xcode 的特性來挂載(hook)到 Build Starts 和 Build Succeeds 上)。由于編譯時間非常依賴于編譯它的裝置,是以我也 git ignored 了 buildtimes.log 檔案。

接下來,我想突出哪些個别代碼塊耗費了額外的長時間來編譯,以便識别瓶頸,這樣我就可以修複它。要做到這個,隻需要通過向 Xcode 中 Build Setting 裡的 Other Swift Flags 傳遞下面的參數給 Swift 編譯器來設定一個臨界值:

使用上面的參數後,在你的項目中,如果有任何函數耗費了超過 500 毫秒的編譯時間,你就會得到一個警告。這是我開始設定的臨界值(并且随着我對更多瓶頸的修複,這個值在不斷的降低)。

在設定了函數編譯時間過長的警告之後,你可能會在項目中開始發現一些。最開始,你會覺得編譯時間過長的函數是随機的,但是很快模式(patterns)就開始出現了。這裡我注意到了兩個使 Swift 3.0 編譯器編譯函數時間過長的常見模式:

自定義運算符(特别是帶有通用參數的重載)

當 Swift 出現時,對于大多數 iOS 和 macOS 開發者來說,運算符重載是全新的概念之一,但就像許多新鮮事物一樣,我們很興奮的使用它們。現在,我不打算在這讨論自定義或重載運算符是好是壞,但它們的确對編譯時間有很大影響,尤其是如果使用更加複雜的表達式。

思考下面的運算符,它将兩個 IntegerConvertible 類型的數字加起來,構成了自定義的數字類型:

然後我們用它來讓幾個數字相加:

看上去很簡單,但是上面的 addNumbers() 函數會花費很長一段時間來編譯(在我 2013 年的 MBP 上超過 300 ms)。對比一下,如果我們用協定擴充來實作相同邏輯:

通過這個改變,我們的 addNumbers() 函數現在編譯時間不到 1 ms。這快了 300 倍!

是以,如果你大量的使用了自定義/重載運算符,特别是帶有通用參數的(或者如果你使用的第三方庫來做這些,比如許多自動布局的庫),考慮一下用普通函數、協定擴充或其他的技術來重寫吧。

集合字面量

另一個我發現的編譯時間瓶頸是使用集合字面量,特别是編譯器需要做很多工作來推斷那些字面量的類型。讓我們假設你有一個函數,它要把模型轉換成一個類似 JSON 的字典,像這樣:

上面 toJSON() 函數在我的電腦上大概要 500 ms 的時間來編譯。現在讓我們試着逐行重構這個像字典的東西來代替字面量:

它現在編譯時間大概在 5 ms 左右,提高了 100 倍!

上面的兩個例子非常清晰的說明了 Swift 編譯器的一些新特性,比如類型推演和重載,都是付出了時間開銷。如果我們仔細思考一下,也很符合邏輯。由于編譯器不得不做更多的工作來執行推演,是以花費了更多的時間。但是我們也看到了,如果我們稍微調整一下我們的代碼,幫助編譯器更簡單的解決表達式,我們就可以很大程度的加快編譯時間。

現在,我不是說你要一直讓編譯時間來決定你寫代碼的方式。有時可以讓它做更多的工作,讓你的代碼更加清晰并且容易了解。但是在大型的項目中,每個函數要用 300-500 ms 範圍(或更多)的時間來編譯的編碼技術可能很快就會成為一個問題。我的建議是對你的編譯時間保持監控,使用上面的編譯标記設定一個合理的臨界值,并在發現問題的時候解決問題。

<b>原文釋出時間為:2017年4月01日</b>

<b>本文來自雲栖社群合作夥伴掘金,了解相關資訊可以關注掘金網站。</b>