(WWDC) 優化Swift性能 (Optimizing Swift Performance)



主要從三個方面進行優化:

  • 引用計數(Reference Counting)
  • 泛型(Generics)
  • 動態分發(Dynamic Dispatch)

最后,也會介紹一下如何使用Time Profiler檢測耗時操作。



引用計數(Reference Counting)



使用類定義一個數據類型,在對這個數據類型進行大量重復操作時,引用計數對于性能的影響將會變得很明顯:

class Point {
    var x, y: Float
}

var array: [Point] = ...
for p in array {
    increment
    ...
    decrement
}

使用struct來解決引用計數導致的性能問題:

struct Point {
    var x, y: Float
}



當struct中也有引用類型時,比如:

包含多個引用的struct

可以用一個類來包裹這個struct,這樣就可以避免對struct中的引用對象進行單獨的引用計數:


用一個類來包裹含有引用對象的struct




泛型(Generics)


觀察以下泛型代碼:

func min<T: Comparable>(x: T, y: T) -> T {
    return y < x ? y : x
}



在編譯時,以上泛型代碼其實是被翻譯成了類似這種結構的代碼:

泛型需要進行轉換后,才能進行后續操作



當泛型函數在被調用時,編譯器會將泛型具體化(Generic Specialization):

泛型具體化

最后被具體化為如下形式:


泛型被具體化為Int類型

但是泛型定義的可見性會影響泛型的具體化,比如在同一個module下的不同文件中:


調用泛型函數的文件無法知曉泛型函數的定義

如何解決泛型定義的可見性問題?

打開項目設置,Build Settings - Optimization Level,將Release模式的選項改為 Whole Module Optimization。此時,同個Module下不同文件中的泛型函數,在開啟Whole Module Optimization之后可以一起編譯,此時泛型定義對于同個Module中的不同文件是可見的。




動態分發(Dynamic Dispatch)



這里涉及到兩個概念:

  • 繼承
  • 訪問控制


繼承

請觀察以下類型的定義:


在獲取name屬性的值和調用noise()方法時,都需要進行額外的操作,因為編譯器不確定屬性和方法有沒有被子類重寫。



如何消除額外的操作?

使用final關鍵字定義var name

使用final關鍵字,明確地告訴編譯器不存在重寫操作。




訪問控制

再來觀察另一個例子:


func noiseImpl() 方法

—— 如果調用noiseImpl()方法,編譯器能不能直接調用Pet中的noiseImpl()方法?
—— 當然不能!

在這里,編譯器會假設子類中有可能重寫這個方法,所以它不會直接調用Pet中的noiseImpl()方法。
這時候,可以通過將noiseImpl()方法定義為private級別來告訴編譯器,直接調用Pet中的noiseImpl()方法。

將方法定義為private級別



對于通過module中的類繼承,同樣可以開啟Whole Module Optimization來避免多余的間接調用。編譯器可以自行判定當前Module中的繼承鏈關系。

開啟Whole Module Optimization前,Dog中的bark方法需要從繼承鏈中找出合適的noise方法
開啟Whole Module Optimization后,Dog中的bark方法直接調用Dog中的noise方法




使用Time Profiler檢測耗時操作

新建一個iOS項目,將Point定義為class類型,然后在ViewController中放置一個按鈕,點擊按鈕即可執行iteratePoints操作。

在ViewController中放置一個按鈕
class Point {
    var x, y: Float
    init(x: Float, y: Float) {
        self.x = x
        self.y = y
    }
}

class ViewController: UIViewController {

    let points = [Point].init(repeating: Point.init(x: 0, y: 0), count: Int(1e7))
    var countX: Float = 0

    @IBAction func iteratePoints(_ sender: Any) {
        for _ in 0..<5 {
            for point in points {
                let p = point
                countX += p.x
            }
        }
    }
}

代碼添加完畢,然后在Xcode中按下Command + I即可啟動Profile,或者可以點擊Xcode菜單欄Product - Profile。彈出Instruments后,雙擊啟動Time Profiler。

啟動Profile
啟動Time Profiler
點擊啟動按鈕



等待CPU占用率變為0,防止其他操作對待檢測的操作造成影響。如下圖紅色區域:

等待CPU占用率變為0(藍色柱消失)

然后,點擊ViewController中的iteratePoints按鈕,即可看到Time Profiler中有明顯的藍色柱出現。


明顯的藍色柱

點擊該區域的藍色柱,然后按下Command和+按鈕,對該區域進行放大。


放大后的效果

拖動選中想要檢查的部分:


選中CPU占用率很高的區域

可以發現底部有部分視圖有變化,726 @objc ViewController.iteratePoints會變為白色,點擊它!


點擊 726 @objc ViewController.iteratePoints



點擊展開@objc ViewController.iteratePoints(:),會看到主要的耗時操作為retain/release

耗時操作為retain/release



現在,關閉當前的Time Profiler(一定要關閉,不需要保存)

關閉當前的Time Profiler




接下來請修改代碼,將Point由class類型改為struct類型。
然后再次按下Command + I,Xcode會用最新的代碼更新Build內容,然后啟動Profile。
按照之前的操作流程進行操作后,你會發現,耗時操作不再是retain/release。

耗時操作為ViewController.iteratePoints(:)



參考文章頁面內有視頻,視頻結尾部分還有很多關于使用Time Profiler進行Swift代碼優化的示例(雖然并沒有提供示例代碼),建議親自觀看。




總結

  • 使用final關鍵字和訪問控制

    1. 幫助編譯器理解你的類繼承結構;
    2. 已有的代碼可能需要因此而更新;
  • 開啟Whole Module Optimization

  • 使用Instruments - Time Profiler來檢測耗時操作,針對性地進行優化






參考文章:
Optimizing Swift Performance




轉載請注明出處,謝謝~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372