Swift 泛型底層

首先我們來看一段代碼

protocol Drawable {
    func draw()
}

class Student: Drawable {
    var x: Int = 0
    var y: Int = 0
    func draw() {
        
    }
}

struct Point: Drawable {
    var x: Int = 0
    var y: Int = 0
    func draw() {
        
    }
}

func foo<T: Drawable>(local: T)  {  
bar(local: local) 
}
func bar<T: Drawable>(local: T) {}


let point = Point()
foo(local: point)

上述代碼中,泛型方法的調用過程大概如下:

// 將泛型T綁定為調用方使用的具體類型铝阐,這里為Point
foo(point) --> foo<T = Point>(point)   
// 在調用內部bar方法時址貌,會使用foo已經綁定的變量類型Point,可以看到徘键,
   泛型T在這里已經被降級练对,通過類型Point進行取代
bar(local)  --> bar<T = Point>(local) 
泛型和Protocol Type的區(qū)別在于:

泛型類型由于在調用時能夠確定具體的類型,每個調用上下文只有一種類型吹害。foo和bar方法是同一種類型螟凭,在調用鏈中會通過類型降級進行類型取代。
在調用泛型方法時它呀,只需要將 Value Witness Table/ Protocol Witness Table 作為額外參數進行傳遞螺男,所以不需要使用 Extential Container。

生命周期管理 Value Witness Table

泛型類型使用 Value Witness Table 進行生命周期管理纵穿,Value Witness Table 由編譯器生成下隧,其存儲了該類型的 size、aligment(對齊方式)以及針對該類型的基本內存操作谓媒。其結構如下所示(以 C 代碼表示):

struct value_witness_table {
    size_t size, align;
    void (*copy_init)(opaque *dst, const opaque *src, type *T);
    void (*copy_assign)(opaque *dst, const opaque *src, type *T);
    void (*move_init)(opaque *dst, const opaque *src, type *T);
    void (*move_assign)(opaque *dst, const opaque *src, type *T);
    void (*destroy)(opaque *val, type *T);
}

注意點:

對于一個小的值類型淆院,如:integer。該類型的 copy 和 move 操作會進行內存拷貝句惯;destroy 操作則不進行任何操作土辩。

對于一個引用類型,如:class抢野。該類型的 copy 操作會對引用計數加 1拷淘;move 操作會拷貝指針,而不會更新引用計數指孤;destroy 操作會對引用計數減 1辕棚。

Value Witness Table.png

函數調用 Protocol Witness Table

func f<T>(_ t: T) -> T {
    let copy = t
    return copy
}

編譯器對上述的泛型函數進行編譯后,會得到如下代碼

void f(opaque *result, opaque *t, type *T) {
     opaque *copy = alloca(T->vwt->size);
     T->vwt->copy_init(copy, t, T);
     T->vwt->move_init(result, copy, T);
     T->vwt->destroy(t, T);
 }

從生成的代碼中可以看出邓厕,方法運行時會傳入一個 type *T。很明顯扁瓢,這是一個類型參數详恼,描述泛型類型所綁定的具體類型的元信息,包括對 Value Witness Table 的索引信息引几。

步驟如下:

  1. 局部變量是分配在棧上的昧互,并且對于該類型挽铁,我們不知道要分配多少內存空間,所以需要通過 Value Witness Table 獲取到 T 的 size 才能進行內存分配敞掘。
  2. 內存空間分配完之后叽掘,通過 Value Witness Table 中的 copy 方法,以輸入值 t 來初始化局部變量玖雁。
  3. 局部變量初始化完畢之后更扁,通過 Value Witness Table 中的 move 方法,將局部變量移到 result 緩沖區(qū)以返回結果赫冬。
  4. 返回時浓镜,通過 Value Witness Table 中的 destroy 方法銷毀局部變量。

敲敲小黑板劲厌,兄die注意了!
type *T 是整個函數能夠順利運行的關鍵膛薛,那么 type *T 到底是什么呢?

編譯器會盡量在編譯時為每一個類型生成一個類型元信息對象——Type Metadata补鼻,也就是上述的 type *T哄啄。

Type Metadata

對于泛型類型來說,通過 Type Metadata 也可以索引到 Value Witness Table!
攜帶的類型元信息主要包含:類型的 Value Witness Table风范、類型的反射信息咨跌。如圖所示:


Type Metadata

每一種類型,在全局只有一個 Type Metadata,供全局共享贝奇。

對于內建基本值類型弥臼,如:Integer,編譯器會在標準庫中生成對應的 Type Metadata 拳喻。其中Value Witness Table 是針對小的值類型 Value Witness Table。

對于引用類型猪腕,如:UIView冗澈,編譯器也會在標準庫中生成 Type Metadata。其中Value Witness Table 是針對引用類型的標準 Value Witness Table陋葡。

對于自定義的引用類型亚亲,Type Metadata 會在我們的程序中生成,Value Witness Table 則由所有引用類型共享腐缤。

編譯后的代碼是如何使用 Type Metadata 的捌归。如下所示為兩種類型對 f<T> 的調用

struct MyStruct {
    var a: UInt8 = 0
    var b: UInt8 = 0
}

f(123)

f(MyStruct())

當使用 int 類型和 MyStruct 類型調用 f<T> 時,編譯器生成的代碼如下所示

 int val = 123;
 extern type *Int_metadata;
 f(&val, Int_metadata);


 MyStruct val;
 type *MyStruct_metadata = { ... };
 f(&val, MyStruct_metadata);

通過上述代碼可以發(fā)現(xiàn) 兩者的區(qū)別在于:
int 類型使用標準庫中的 Type Metadata岭粤;
自定義類型則使用針對自身生成的 Type Metadata惜索。

上述 Type Metadata 之所以能夠在編譯時生成,是因為我們在調用時就能通過類型推導得出其類型剃浇。如果巾兆,在調用時無法推斷其類型猎物,則需要在運行時動態(tài)生成 Type Metadata!

Type Metadata 的動態(tài)生成,我們需要先來了解 Metadata Pattern

對于泛型類型角塑,編譯器會在編譯時生成一個 Metadata Pattern蔫磨。
Metadata Pattern 與 Type Metadata 的關系其實就是類與對象的關系。

以如下自定義泛型類結構為例:

 struct Pair<T> {
     var first: T
     var second: T
 }

 let pa = Pair(first: 1, second: 5)

運行時根據綁定類型的 Type Metadata圃伶,結合 Metadata Pattern堤如,生成最終的確定類型的 Type Metadata。如圖所示:


Type Metadata.png
  • 編譯時生成一個 Pair Metadata Pattern
  • 可以看出Pair<T> 為int類型, 在運行時根據綁定類型的 (Int)Type Metadata留攒,并結合 Metadata Pattern煤惩,生成最終的確定類型的 Type Metadata

我們通過一個泛型屬性訪問的例子來看看運行時是如何使用 Metadata Pattern 來生成 Type Metadata

func getSecond<T>(_ pair: Pair<T>) -> T {
    return pair.second
}

編譯器生成的代碼如下:

 void getSecond(opaque *result, opaque *pair, type *T) {
 
     實例化 type metadata
     type *PairOfT = get_generic_metadata(&Pair_pattern, T);
 
     根據 Pair Type Metadata, 根據偏移字段, 獲得 second 在內存中的位置。
     const opaque *second = (pair + PairOfT->fields[1]);
 
     拷貝 second 在位置的內存到 result 緩存區(qū)  ( 緩存區(qū): 函數內部 { result } )
     T->vwt->copy_init(result, second, T);
 
     返回前炼邀,銷毀局部變量魄揉。
     PairOfT->vwt->destroy(pair, PairOfT);
 }

在泛型類型調用方法時, Swift 會將泛型綁定為具體的類型拭宁。在編譯時就能推導出泛型類型洛退,編譯器則會進行優(yōu)化,提高運行性能! 在運行時避免通過傳遞 Type Metadata 來查找各個域的偏移杰标,從而提高運行性能!因此該實現(xiàn)的是靜態(tài)多態(tài)兵怯。在調用時能夠確定具體的類型,所以不需要使用 Extential Container腔剂。

但在協(xié)議類型調用方法時媒区,類型是 Existential Container,需要在方法內部進一步根據 Protocol Witness Table 進行方法索引掸犬,因此協(xié)議實現(xiàn)的是動態(tài)多態(tài)袜漩。

泛型特化

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

let a: Int = 1
let b: Int = 2
min(a, b)

上述代碼,編譯器在編譯期間就能通過類型推導確定調用 min 方法時的類型湾碎。此時宙攻,編譯器就會通過泛型特化,進行 類型取代(Type Substitute)介褥,生成如下的代碼:

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

靜態(tài)多態(tài)在調用棧中只有一種類型!

在只有一種類型的特點座掘,來進行類型降級取代。類型降級后柔滔,產生特定類型的方法溢陪,為泛型的每個類型生成一個對應的方法。 即使這樣睛廊,我們也不用擔心會出現(xiàn)代碼空間溢出的情況嬉愧,因為是靜態(tài)多態(tài),可以進行內聯(lián)實現(xiàn)喉前,并且通過獲取上下文來進行更進一步的優(yōu)化没酣。從而降低方法數量,優(yōu)化后可以更精確卵迂,并提高性能裕便。

泛型特化是何時發(fā)生的?

在使用優(yōu)化時,調用方需要進行類型推斷见咒,這里需要知曉類型的上下文偿衰,例如類型的定義和內部方法實現(xiàn)。如果調用方和類型是單獨編譯的改览,就無法在調用方推斷類型的內部實行下翎,就無法使用優(yōu)化。

為保證這些代碼一起進行編譯宝当,這里就用到了whole module optimization视事。

而whole module optimization是對于調用方和被調用方的方法在不同文件時,對其進行泛型特化優(yōu)化的前提庆揩。

whole module optimization (全模塊優(yōu)化)

whole module optimization是用于Swift編譯器的優(yōu)化機制俐东,從 Xcode 8 開始默認開啟。


generate

編譯器在對源文件進行語法分析之后订晌,會對其進行優(yōu)化虏辫,生成機器碼并輸出目標文件,之后鏈接器聯(lián)合所有的目標文件生成共享庫或可執(zhí)行文件锈拨。

whole module optimization

whole module optimization通過跨函數優(yōu)化砌庄,可以進行內聯(lián)等優(yōu)化操作,對于泛型奕枢,可以通過獲取類型的具體實現(xiàn)來進行推斷優(yōu)化娄昆,進行類型降級方法內聯(lián),刪除多余方法等操作验辞。

全模塊優(yōu)化的優(yōu)勢:

  • 編譯器掌握所有方法的實現(xiàn)稿黄,可以進行內聯(lián)和泛型特化等優(yōu)化,通過計算所有方法的引用跌造,移除多余的引用計數操作杆怕。
  • 通過知曉所有的非公共方法,如果方法沒有被使用壳贪,就可以對其進行消除陵珍。

那么弊端則是會增加編譯時間

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市违施,隨后出現(xiàn)的幾起案子互纯,更是在濱河造成了極大的恐慌,老刑警劉巖磕蒲,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件留潦,死亡現(xiàn)場離奇詭異只盹,居然都是意外死亡,警方通過查閱死者的電腦和手機兔院,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門殖卑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人坊萝,你說我怎么就攤上這事孵稽。” “怎么了十偶?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵菩鲜,是天一觀的道長。 經常有香客問我惦积,道長接校,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任荣刑,我火速辦了婚禮馅笙,結果婚禮上,老公的妹妹穿的比我還像新娘厉亏。我一直安慰自己董习,他們只是感情好,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布爱只。 她就那樣靜靜地躺著皿淋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪恬试。 梳的紋絲不亂的頭發(fā)上窝趣,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機與錄音训柴,去河邊找鬼哑舒。 笑死,一個胖子當著我的面吹牛幻馁,可吹牛的內容都是我干的洗鸵。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼仗嗦,長吁一口氣:“原來是場噩夢啊……” “哼膘滨!你這毒婦竟也來了?” 一聲冷哼從身側響起稀拐,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤火邓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體铲咨,經...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡躲胳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了纤勒。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泛鸟。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖踊东,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情刚操,我是刑警寧澤闸翅,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站菊霜,受9級特大地震影響坚冀,放射性物質發(fā)生泄漏。R本人自食惡果不足惜鉴逞,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一记某、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧构捡,春花似錦液南、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至喘帚,卻和暖如春畅姊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背吹由。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工若未, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人倾鲫。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓粗合,卻偏偏與公主長得像,于是被迫代替她去往敵國和親级乍。 傳聞我的和親對象是個殘疾皇子舌劳,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355