編寫高性能 Swift - 減少動(dòng)態(tài)分發(fā)的三條建議

高性能代碼是每個(gè)開發(fā)工程師應(yīng)有的追求。
在 Swift 官方 Github 上,官方整理了一些編寫高性能 Swift 代碼的技巧,這些技巧可以幫助提高您的Swift程序的質(zhì)量,并使代碼更不易出錯(cuò),更易讀。值得我們好好研讀。
小集后續(xù)會(huì)陸續(xù)整理這些內(nèi)容,同時(shí)也會(huì)搜集這一類的好文章,期望能給 Swifter 帶來(lái)幫助。
Swift 與 Objective-C 一樣,是一種高度動(dòng)態(tài)化的語(yǔ)言。不過(guò)與 Objective-C 不同的是,Swift 能夠在必要時(shí)通過(guò)消除或減少這種動(dòng)態(tài)性來(lái)提高運(yùn)行時(shí)性能。本節(jié)將介紹幾個(gè)可用于執(zhí)行這類操作的語(yǔ)言構(gòu)造。
動(dòng)態(tài)分發(fā)
默認(rèn)情況下,Swift 中類的方法和屬性訪問(wèn)使用動(dòng)態(tài)分發(fā)的方式。因此,在下面的代碼段中,a.aProperty、a.doSomething() 和 a.doSomethingElse() 都是通過(guò)動(dòng)態(tài)分發(fā)來(lái)調(diào)用的。
class A {
var aProperty: [Int]
func doSomething() { ... }
dynamic doSomethingElse() { ... }
}
class B: A {
override var aProperty {
get { ... }
set { ... }
}
override func doSomething() { ... }
}
func usingAnA(_ a: A) {
a.doSomething()
a.aProperty = ...
}
在 Swift 中,動(dòng)態(tài)分發(fā)默認(rèn)通過(guò) vtable 來(lái)間接調(diào)用。如果在聲明中附加 dynamic 關(guān)鍵字,那么 Swift 將以 Objective-C 消息分發(fā)機(jī)制來(lái)發(fā)出調(diào)用。這兩種情況都比直接函數(shù)調(diào)用的方式慢,因?yàn)樗鼈兂藞?zhí)行間接調(diào)用本身的開銷外,還阻止了許多編譯器優(yōu)化。
所以,在對(duì)性能很敏感的應(yīng)用中,我們通常會(huì)希望限制這種動(dòng)態(tài)行為。
建議1:當(dāng)知道不需要重寫聲明時(shí),請(qǐng)使用 final
final 關(guān)鍵字是對(duì)類、方法或?qū)傩缘穆暶鞯南拗?,以使聲明不能被覆蓋。這意味著編譯器可以使用直接函數(shù)調(diào)用,而不是間接調(diào)用。例如,在下面的示例中,將直接訪問(wèn) C.array1 和 D.array1。相比之下,D.array2 將通過(guò) vtable 進(jìn)行調(diào)用:
final class C {
// No declarations in class 'C' can be overridden.
var array1: [Int]
func doSomething() { ... }
}
class D {
final var array1: [Int] // 'array1' cannot be overridden by a computed property.
var array2: [Int] // 'array2' *can* be overridden by a computed property.
}
func usingC(_ c: C) {
c.array1[i] = ... // Can directly access C.array without going through dynamic dispatch.
c.doSomething() = ... // Can directly call C.doSomething without going through virtual dispatch.
}
func usingD(_ d: D) {
d.array1[i] = ... // Can directly access D.array1 without going through dynamic dispatch.
d.array2[i] = ... // Will access D.array2 through dynamic dispatch.
}
建議2:當(dāng)不需要在文件外部訪問(wèn)聲明時(shí),請(qǐng)使用 private 和 fileprivate
將 private 或 fileprivate 關(guān)鍵字應(yīng)用于聲明會(huì)將聲明的可見性限制在文件中。這讓編譯器能夠確定所有其他可能覆蓋的聲明。因此,如果文件中沒(méi)有任何重寫的聲明,那么編譯器能夠自動(dòng)推斷并使用 final 關(guān)鍵字,并相應(yīng)地刪除對(duì)方法和字段訪問(wèn)的間接調(diào)用。例如,在下面的示例中,假設(shè) E,F(xiàn) 在同一文件中沒(méi)有任何重寫的聲明,則可以直接訪問(wèn) e.doSomething() 和 f.myPrivateVar:
private class E {
func doSomething() { ... }
}
class F {
fileprivate var myPrivateVar: Int
}
func usingE(_ e: E) {
e.doSomething() // There is no sub class in the file that declares this class.
// The compiler can remove virtual calls to doSomething()
// and directly call E's doSomething method.
}
func usingF(_ f: F) -> Int {
return f.myPrivateVar
}
建議3:如果啟用了 WMO,則當(dāng)不需要在模塊外部訪問(wèn)聲明時(shí),請(qǐng)使用 internal
WMO 讓編譯器一次編譯所有模塊的源代碼。這使優(yōu)化器在編譯單個(gè)聲明時(shí)具有模塊范圍的可見性。由于內(nèi)部聲明在當(dāng)前模塊之外不可見,因此優(yōu)化器可以通過(guò)自動(dòng)發(fā)現(xiàn)所有可能重寫的聲明來(lái)推斷出 final。
注意:由于在 Swift 中默認(rèn)訪問(wèn)控制級(jí)別始終是 internal,因此通過(guò)啟用“整體模塊優(yōu)化”,無(wú)需進(jìn)行任何其他工作即可獲得更多的虛擬化功能。
