函式派發就是程式判斷使用哪種途徑去呼叫乙個函式的機制. 每次函式被呼叫時都會被觸發, 但你又不會太留意的乙個東西.
了解派發機制對於寫出高效能的**來說很有必要, 而且也能夠解釋很多 swift 裡」奇怪」的行為.
編譯型語言有三種基礎的函式派發方式:
直接派發(direct dispatch),
函式表派發(table dispatch)
訊息機制派發(message dispatch)
大多數語言都會支援一到兩種
這樣的方式非常好, 但也給很多開發者帶來了困擾,
程式派發的目的是為了告訴 cpu 需要被呼叫的函式在** 先來了解一下這三種派發方式, 以及每種方式在動態性和效能之間的取捨.
直接派發是最快的, 不止是因為需要呼叫的指令集會更少, 並且編譯器還能夠有很大的優化空間, 例如函式內聯等, 直接派發也有人稱為靜態呼叫.
然而, 對於程式設計來說直接呼叫也是最大的侷限, 而且因為缺乏動態性所以沒辦法支援繼承. c語言就是典型的直接派發方式,沒有那麼多彎彎繞。
在swift中給函式加上final關鍵字,該函式也會變成直接派發的方式。
函式表派發是編譯型語言實現動態行為最常見的實現方式. 函式表使用了乙個陣列來儲存類宣告的每乙個函式的指標. 大部分語言把這個稱為 「virtual table」(虛函式表), swift 裡稱為 「witness table」. 每乙個類都會維護乙個函式表, 裡面記錄著類所有的函式,如果父類函式被 override 的話, 表裡面只會儲存被 override 之後的函式. 乙個子類新新增的函式, 都會被插入到這個陣列的最後. 執行時會根據這乙個表去決定實際要被呼叫的函式.
class
parentclass
func
method2()
}class
childclass
:parentclass
func
method3()
}
如上面的兩個類,在這種情況下編譯器會建立兩個函式表,乙個是parentclass的,乙個是childclass,下面的兩張表展示了 parentclass 和 childclass 虛數表裡 method1, method2, method3 在記憶體裡的布局:
0xa00
parentclass
0x121
method1
0x122
method2
0xb00
childclass
0x121
method1(繼承了父類)
0x222method2
0x223method3
當乙個函式被呼叫時:
let obj =
childclass()
obj.
method2
()
會經歷下面的幾個過程:
讀取物件 0xb00 的函式表.
讀取函式指標的索引. 在這裡, method2 的索引是1(偏移量), 也就是 0xb00 + 1.
跳到 0x222 (函式指標指向 0x222)
查表是一種簡單, 易實現, 而且效能可預知的方式. 然而, 這種派發方式比起直接派發還是慢一點. 從位元組碼角度來看, 多了兩次讀和一次跳轉, 由此帶來了效能的損耗. 另乙個慢的原因在於編譯器可能會由於函式內執行的任務導致無法優化. (如果函式帶有***的話)
這種基於陣列的實現, 缺陷在於函式表無法拓展. 子類會在虛數函式表的最後插入新的函式, 沒有位置可以讓 extension 安全地插入函式.
舉個例子, 看看下面兩個類:
class
parentclass
dynamic
func
method2()
}class
childclass
:parentclass
dynamic
func
method3()
}
swift 會用樹來構建這種繼承關係:
當乙個訊息被派發,執行時會順著類的繼承關係向上查詢應該被呼叫的函式.
如果你覺得這樣做效率很低(它確實很低),然而, 只要快取建立了起來, 這個查詢過程就會通過快取來把效能提高到和函式表派發一樣快. 但這只是訊息機制的原理, 這裡有一篇文章很深入的講解了具體的技術細節.
再探swift函式的派發方式
Swift函式派發優化
與許多其他語言一樣,swift允許類重寫其父類中宣告的方法和屬性。這意味著程式必須在執行時確定要引用哪個方法或屬性,然後執行間接呼叫或間接訪問。這種稱為動態排程的技術以每次間接使用時恆定的執行時開銷為代價提高了語言的表達能力。在對效能敏感的 中,我們不希望花費這些開銷。這篇部落格文章展示了通過消除這...
Swift 的函式派發理解
參考 對於編譯型的語言,函式派發的三種基礎模式分別是 直接派發 direct dispatch 也可以理解外,直接呼叫 不需要曲線救國 函式表派發 table dispatch 物件導向的語言中,一般都會有類的概念,類 用乙個陣列來儲存類中的函式指標,通過此列表 去找 對應的函式 這種派發 催生了 ...
初探Swift3 0帶來的變化彙總
廢話不多說了,直接給大家貼 了。var string hello swift 獲取某個下標後乙個下標對應的字元 char e var char string startindex.successor var char string string.index after startindex 獲取某個...