通過減少動態派送提升效能

nathanw發表於2015-05-12

和其他許多語言一樣,Swift 允許一個類的子類重寫方法和屬性。這就意味著程式必須在執行時決定關聯哪個方法或屬性,從而間接呼叫或間接訪問。這項叫動態派送的技術,以每次間接呼叫固定的執行時開銷為代價,增強了語言的表達性。如果對於效能敏感,這種方法往往是不可取的。這篇文章將介紹3種消除動態機制來提升效能的方法:final, private 和全域性模組優化。

先細讀下面例子:

如上所寫,編譯器會建立一個動態派送呼叫:

  • 呼叫 update()
  • 呼叫 updatePoint()
  • Get 屬性 point 元組,
  • Get 屬性 velocity

當你看這段程式碼時,這可能和你預想的不一樣。因為 ParticleModel 的子類可能使用計算屬性重寫 pointvelocity,或者重寫 updatePoint()update() 方法,因此動態呼叫是必要的。

在 Swift,執行動態派送呼叫就是在方法列表裡尋找函式,然後間接呼叫。這會慢於直接呼叫。另外,間接呼叫阻止了許多編譯器優化,使得使用間接呼叫成本更高。在效能要求嚴格的情況下,這裡有一些技術可以在不需要動態行為的時候提升效能。

當你確定宣告不需要被重寫時,使用 final

關鍵字 final 可以標示類、方法和屬性的宣告(declaration)不能被重寫。編譯器便會安全地省略動態派送。例如在下面這個程式碼,訪問 pointvelocity 會直接載入物件的儲存屬性,並且呼叫 updatePoint() 是直接呼叫。另一方面,update() 還是會通過動態派送呼叫,也允許子類重寫 update()

把整個類標記 final 也是可行的,只需加在 class 前面。這會禁止子類繼承,隱式地宣告該類所有的函式和屬性都是 final

通過應用 private 關鍵字,把僅在一個檔案中出現的宣告推斷為 final

給宣告(declaration)應用 private 關鍵字會把它限制為僅在當前檔案可見。這允許編譯器去尋找所有潛在可重寫的宣告。未存在重寫的宣告會使得編譯器自動推斷為 final,去除訪問方法和屬性時的間接呼叫。

假設沒有一個類在當前檔案重寫了 ParticleModel,編譯器會將所有 private 宣告的動態派送呼叫替換為直接呼叫。

上面這個例子,pointvelocity 會被直接訪問,updatePoint() 會被直接呼叫。而 update() 則再一次被間接呼叫,因沒有 private 標記。

final 一樣,給類宣告標記 private 也是可以的,這回使得類是私有的,它的所有屬性和方法也是私有的。

使用全域性模組優化,將 internal 宣告推斷為 final

帶有 internal 訪問的宣告(如果沒有顯式宣告,預設也是如此)只有在當前模組才可見。因為 Swift 通常會分開編譯同一模組的不同檔案,編譯器不能推斷一個 internal 宣告會不會在其他檔案中被重寫。但是,如果全域性模組優化可用,所有的模組會在同一時間被編譯。這就允許編譯器將所有模組一起處理,如果 internal 宣告沒有可見的重寫,就推斷為 final。

回到最開始的程式碼,這次我們給 ParticleModel 加些 public 關鍵字。

當用全域性模組優化編譯這段程式碼,編譯器會推斷屬性 point,velocity 和方法 updatePoint()final。相對的,update() 被標記為 public,不會被推斷為 final

相關文章