(WWDC) 理解 Swift 性能


理解如何實(shí)現(xiàn)才能更好地去理解性能


內(nèi)容概覽

  • 內(nèi)存分配
  • 引用計(jì)數(shù)
  • 方法分發(fā)
  • 協(xié)議類型
  • 泛型代碼
  • 總結(jié)


考慮性能的維度





內(nèi)存分配


在棧上分配、回收內(nèi)存時(shí),只需要移動(dòng)棧指針即可完成操作。操作的成本如同給一個(gè)整型變量賦值。

在堆上分配內(nèi)存時(shí),需要在復(fù)雜的堆數(shù)據(jù)結(jié)構(gòu)中尋找未被使用的大小適合的內(nèi)存。
在堆上回收內(nèi)存時(shí),需要找到合適的位置,然后才能插入內(nèi)存。

很明顯,在堆上操作的成本比在棧上的更高。

除此之外,多個(gè)線程可以同時(shí)在堆上分配內(nèi)存,所以堆需要使用鎖或其他同步機(jī)制以保證完整性。
這就導(dǎo)致了更嚴(yán)重的性能消耗。


內(nèi)存分配示例



使用 struct

定義 Point 并使用
為 point1 和 point2 分配棧內(nèi)存
將 Point 賦值給 point1
將 point1 賦值給 point2
更改 point2 的值
執(zhí)行結(jié)束,回收內(nèi)存



使用 class

將 struct 改為 class
為 point1 和 point2 分配棧內(nèi)存
在堆上為 Point 分配內(nèi)存
讓 point1 指向 Point 實(shí)例
將 point2 指向 point1 指向的 Point 實(shí)例
更改 point2 指向的 Point 實(shí)例的值
釋放 Point 實(shí)例的堆內(nèi)存
釋放 point1 和 point2 的棧內(nèi)存



使用 struct 優(yōu)化內(nèi)存分配

將 String 作為 Key

請(qǐng)注意,String中的字符(Character)存儲(chǔ)在堆內(nèi)存中!


定義 struct 作為 Key

這里,使用 struct 保證內(nèi)存分配在棧上進(jìn)行,使代碼更安全也更高效!





引用計(jì)數(shù)


引用計(jì)數(shù)除了加減操作,還有以下操作:

  • 間接操作
  • 線程安全
Swift 偽代碼

為了執(zhí)行 retain, release 操作,Swift 需要在堆內(nèi)存中進(jìn)行引用計(jì)數(shù)。

refCount 增加
refCount 增加
refCount 減少
refCount 減少
釋放堆內(nèi)存


優(yōu)化引用計(jì)數(shù)的示例
String 類型需要使用堆內(nèi)存
將 uuid 定義為 UUID 值類型
將 mimeType 定義為 MimeType 枚舉類型





方法分發(fā)


方法分發(fā)分為兩種:

  • 靜態(tài)分發(fā)
    1. 在運(yùn)行時(shí),直接運(yùn)行方法的實(shí)現(xiàn)部分
    2. 可以進(jìn)行內(nèi)聯(lián)(代碼直接嵌入)或其他優(yōu)化操作
  • 動(dòng)態(tài)分發(fā)
    1. 在運(yùn)行時(shí),需要在函數(shù)表(V-Table)查找實(shí)現(xiàn)部分
    2. 找到實(shí)現(xiàn)部分后,才能執(zhí)行
    3. 無(wú)法進(jìn)行內(nèi)聯(lián)或其他優(yōu)化操作


內(nèi)聯(lián)優(yōu)化示例
使用 struct
調(diào)用部分被直接替換為函數(shù)的實(shí)現(xiàn)部分

以上優(yōu)化有效地避免了兩個(gè)函數(shù)的棧內(nèi)存分配。


基于繼承的多態(tài)
定義繼承結(jié)構(gòu)
運(yùn)行時(shí)的內(nèi)存布局
在函數(shù)表(V-Table)查找函數(shù)的實(shí)現(xiàn)部分

至此,我們可以看到明顯的性能消耗差異。





協(xié)議類型


沒(méi)有繼承和引用的多態(tài)

使用 protocol 和 struct
Swift 使用 Protocol Witness Table 進(jìn)行協(xié)議的方法分發(fā)

每個(gè)實(shí)現(xiàn)協(xié)議的類型都有一個(gè) Protocol Witness Table (PWT),在這個(gè)表里可以找到函數(shù)的實(shí)現(xiàn)部分。
Swift 使用了一種特殊的內(nèi)存布局 Existential Container 來(lái)存儲(chǔ)實(shí)現(xiàn)了協(xié)議的不同類型。

Existential Container

Existential Container 中的前3個(gè)字(word)是 valueBuffer。

Existential Container

像 Point 這種小類型,只需要占用2個(gè)字。

Existential Container

而 Line 需要4個(gè)字,Swift 使用堆內(nèi)存存儲(chǔ)值,然后在 Existential Container 中存儲(chǔ)值的指針。

因?yàn)?Point 和 Line 類型的差異性,Existential Container 需要使用 Value Witness Table 來(lái)管理這些值的生命周期,每個(gè)類型有一個(gè) Value Witness Table。

Value Witness Table
關(guān)系示意圖
Existential Container 引用 VWT
Existential Container 引用 PWT



被展開(kāi)的執(zhí)行過(guò)程(偽代碼)

如果感興趣,建議下載 官方PDF 查看詳細(xì)流程。



使用間接存儲(chǔ)來(lái)優(yōu)化大的值類型:





泛型代碼


采用泛型的示例代碼



泛型代碼的特征:

  • 靜態(tài)的多態(tài): 在調(diào)用的地方使用具體的類型
  • 每種類型都有單獨(dú)的調(diào)用上下文
  • 隨著調(diào)用鏈替換類型
泛型代碼的類型替換



泛型方法的實(shí)現(xiàn):

  • 共享的實(shí)現(xiàn)
  • 使用 Protocol/Value Witness Table
  • 每種類型都有單獨(dú)的調(diào)用上下文:傳遞 Table



泛型特殊化:

  • 靜態(tài)的多態(tài): 在調(diào)用的地方使用具體的類型
  • 創(chuàng)建類型專屬版本的方法
  • 每個(gè)類型都有自己專屬的方法
  • 可以進(jìn)行壓縮優(yōu)化



何時(shí)進(jìn)行特殊化?

  • 在調(diào)用的地方
  • 定義是可見(jiàn)的

開(kāi)啟 Whole Module Optimization 可以獲得更多優(yōu)化的機(jī)會(huì)。





總結(jié)


使用合適的抽象,以降低動(dòng)態(tài)運(yùn)行時(shí)的消耗

  • 使用值類型:struct
  • 引用類型: 特性 或 面向?qū)ο箫L(fēng)格的多態(tài)
  • 泛型:靜態(tài)的多態(tài)
  • 協(xié)議:動(dòng)態(tài)的多態(tài)

使用間接存儲(chǔ)解決大的值類型的引用問(wèn)題





參考內(nèi)容:
Understanding Swift Performance




轉(zhuǎn)載請(qǐng)注明出處,謝謝~

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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