WWDC2016: 416 Understanding Swift Performance

主要內(nèi)容

講解了Swift中對引用類型和值類型的內(nèi)存管理方式,并提出一些優(yōu)化建議。

影響性能的重要因素

  1. 棧內(nèi)存 vs 堆內(nèi)存
    棧內(nèi)存的分配和釋放僅僅是通過移動棧指針來實現(xiàn)的。而堆內(nèi)存則要根據(jù)申請的大小在堆中尋找合適的位置打颤,釋放時需要將內(nèi)存塊回收到堆中,并且要考慮線程安全漓滔,所以會慢得多瘸洛。Swift中的各種基本數(shù)據(jù)類型和容器(如Int, String, Array, Dictionary等)都是struct,編譯器會盡可能用棧的方式來為它們分配內(nèi)存次和。
  2. 引用計數(shù)
    編譯器會自動在我們的代碼中加入retainrelease的調(diào)用反肋。這兩個函數(shù)的調(diào)用頻率極高且涉及線程安全的控制,使其有可能成為性能瓶頸踏施。
  3. 動態(tài)函數(shù)調(diào)用 vs 靜態(tài)函數(shù)調(diào)用
    動態(tài)函數(shù)調(diào)用就是在運行時才確定函數(shù)名對應(yīng)的函數(shù)地址石蔗。對于動態(tài)調(diào)用,程序必須動態(tài)地查詢函數(shù)地址畅形,而無法在編譯期使用inline等優(yōu)化手段养距,會相對慢一些。

優(yōu)化

struct作為Dictionary的key

Paste_Image.png

此處把3個數(shù)據(jù)點序列化為一個String日熬,并以此為key棍厌。這樣做有兩個缺點:

  1. 使用String為key意味著可以傳入任意String,哪怕不是這3個數(shù)據(jù)點的序列化結(jié)果竖席,比如"abc"或"123"耘纱。
  2. String本身雖然是struct,但它內(nèi)部儲存字符的數(shù)組卻是分配在堆上的毕荐。每次序列化意味著一次堆操作束析。
Paste_Image.png

改為struct可以優(yōu)雅地解決這兩個問題。

盡量使用(純)值類型

Paste_Image.png

值類型如果包含引用類型的數(shù)據(jù)成員憎亚,則程序仍需為此數(shù)據(jù)成員進行引用計數(shù)的維護和堆操作员寇。
所以弄慰,盡可能地使用“純值”類型(這是我造的詞,表示不包含引用類型的值類型蝶锋,編譯器可以實現(xiàn)完全的棧內(nèi)存管理)陆爽。比如以下結(jié)構(gòu)中,有兩個String成員扳缕,程序需為它們進行堆操作墓陈。

Paste_Image.png

把它改成UUIDenum類型

Paste_Image.png

注意:此處的enum的源數(shù)據(jù)類型雖然是String,但是它在內(nèi)存并不是持有一個String(雖然沒說第献,我想應(yīng)該只是個Int)贡必。
所以整個結(jié)構(gòu)變成了純值類型。

感謝 小剛_aea8 的訂正庸毫。此處的Attachment由于還包含了URL類型的成員仔拟,所以它并沒有變成一個“純值”類型。

在不需要多態(tài)時飒赃,使用泛型代替protocol

先說下多態(tài)的基本實現(xiàn)原理利花。

Paste_Image.png

上面的代碼?是通過override的方式來實現(xiàn)多態(tài)的。編譯器為每個對象添加了一個type成員载佳,里面是這個具體類的信息(稱為Virtual Method Table炒事,簡稱V-Table),其中就包含了它override的函數(shù)指針蔫慧,也就是各自的draw函數(shù)挠乳。下面調(diào)用的代碼就是通過查詢V-Table才找到正確的實現(xiàn)的。

Paste_Image.png

上面的代碼把class改為了protocolstruct姑躲。
像前一個例子中那樣睡扬,class類型的數(shù)組的每個成員只占用一個?指針的size,因為只需放入一個指針黍析。而這個例子中卖怜,由于struct是值類型,它被放進數(shù)組時應(yīng)該是拷貝進去的阐枣,所以理論上马靠,它要占用struct自身size的內(nèi)存。那不同size的struct如何被放進同一個數(shù)組呢蔼两?
當(dāng)需要用protocol類型指針來?指向struct時甩鳄,Swift采用了一個叫Existential Container的結(jié)構(gòu)來保存?struct的成員變量和方法。如下圖:

Paste_Image.png

Existential Container的前3個word稱為value buffer宪哩,是用來保存struct的數(shù)據(jù)成員的娩贷。對于比較小的struct可以直接把值塞進去第晰,對于超過3個wordstruct锁孟,則只能分配在堆上彬祖,然后在這里保存一個指針。此時每個struct在棧上占用的空間就一樣了(5個word)品抽,這就解答了上面的問題储笑。接下來,為了統(tǒng)一處理不同size的struct圆恤,又在第4個word增加了一個叫Value Witness Table(簡稱VWT)的結(jié)構(gòu)突倍,里面包含了一組函數(shù)。如下圖:

Paste_Image.png

PointLine為例:

函數(shù) Point Line
allocate 沒動作 在堆上分配內(nèi)存盆昙,并保存指針到value buffer
copy 把值拷到value buffer 把值拷到value buffer中的指針對應(yīng)的堆內(nèi)存上
destruct 如果包含引用類型的成員變量羽历,這里需要?引用減1。此處沒有淡喜,所以沒動作 也沒動作
deallocate 沒動作 釋放堆上的內(nèi)存

注:實際上不止這幾個函數(shù)秕磷,還有allocateBufferAndCopyValueprojectBuffer炼团、destructAndDeallocateBuffer等澎嚣。

Paste_Image.png

在第5個word上添加一個Protocol Witness Table(簡稱PWT)的結(jié)構(gòu)。里面包含protocol的成員方法的指針瘟芝。PWT與V-Table很類似易桃,程序也是通過查詢這個表來實現(xiàn)多態(tài)的。

Paste_Image.png

上圖中锌俱,左上角是源代碼晤郑,左下角是編譯時產(chǎn)生的代碼∶澈辏可以看到:

  • 類型為Drawable的參數(shù)編譯后變成ExistContDrawable贩汉,也就是上面提到的Existential Container。
  • 函數(shù)首先在創(chuàng)建一個ExistContDrawable類型的臨時變量锚赤,用來放參數(shù)的值匹舞。
  • 拷貝type成員(里面包含實現(xiàn)類的信息,圖中寫錯了线脚,應(yīng)該是 local.type = val.type)
  • 拷貝pwt成員(里面包含struct實現(xiàn)的protocol中的方法的函數(shù)地址)
  • 分配空間并賦值
  • 調(diào)用projectBuffer取出數(shù)據(jù)正確的內(nèi)存地址(里面判斷是否需要堆操作赐稽。我想應(yīng)該是從前面的type里面取出實現(xiàn)類的size來判斷的。)
  • 調(diào)用draw函數(shù)浑侥。聲明中的draw方法雖然沒有參數(shù)姊舵,編譯出來后會加上一個參數(shù),就是結(jié)構(gòu)體的實際內(nèi)存地址寓落。
  • 最后調(diào)用destructAndDeallocateBuffer清理內(nèi)存(temp寫錯了括丁,應(yīng)該是local)

一個簡單的調(diào)用實際做了這么多事情。這些代價都是花在需要動態(tài)判斷具體struct的信息和跳轉(zhuǎn)到?對應(yīng)的方法上的伶选。如果改成使用泛型史飞,則編譯器就可以在編譯期知道具體類型了尖昏,也就可以進行諸如inline等優(yōu)化手段。具體實現(xiàn)?參考下面兩張圖:

用`protocol`實現(xiàn)
用泛型實現(xiàn)

但是构资,這樣做的前提是不需要使用多態(tài)抽诉。如果像上面的例子那樣,需要把不同的實現(xiàn)類放進一個數(shù)組中吐绵,則必須借用多態(tài)了迹淌。

使用“寫時拷貝”

由于struct是值類型,當(dāng)它在傳遞時會發(fā)生多次拷貝己单。如果你的struct拷貝成本很高或者拷貝發(fā)生得很頻繁唉窃,而修改卻很少的話,可以考慮使用“寫時拷貝”的方法來優(yōu)化它纹笼,如下:

Paste_Image.png

此處使用一個storageclass來包裝數(shù)據(jù)句携。當(dāng)Line發(fā)生拷貝時,storage成員只發(fā)生引用計數(shù)加一的操作允乐。當(dāng)需要真正寫入時矮嫉,再調(diào)用isUniquelyReferencedNonObjc判斷一下storage的引用計數(shù)是否大于1, 是的話則顯式拷貝一份再進行寫入牍疏。
內(nèi)置類型String,Array,Set,Dictionary均使用了這個技術(shù)蠢笋。

相關(guān)視頻

Paste_Image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市鳞陨,隨后出現(xiàn)的幾起案子昨寞,更是在濱河造成了極大的恐慌,老刑警劉巖厦滤,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件援岩,死亡現(xiàn)場離奇詭異,居然都是意外死亡掏导,警方通過查閱死者的電腦和手機享怀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來趟咆,“玉大人添瓷,你說我怎么就攤上這事≈瞪矗” “怎么了鳞贷?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長虐唠。 經(jīng)常有香客問我搀愧,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任咱筛,我火速辦了婚禮搓幌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘眷蚓。我一直安慰自己鼻种,他們只是感情好反番,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布沙热。 她就那樣靜靜地躺著,像睡著了一般罢缸。 火紅的嫁衣襯著肌膚如雪篙贸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天枫疆,我揣著相機與錄音爵川,去河邊找鬼。 笑死息楔,一個胖子當(dāng)著我的面吹牛寝贡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播值依,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼圃泡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了愿险?” 一聲冷哼從身側(cè)響起颇蜡,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎辆亏,沒想到半個月后风秤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡扮叨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年缤弦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彻磁。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡甸鸟,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出兵迅,到底是詐尸還是另有隱情抢韭,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布恍箭,位于F島的核電站刻恭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鳍贾,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一鞍匾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧骑科,春花似錦橡淑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至斗埂,卻和暖如春符糊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背呛凶。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工男娄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人漾稀。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓模闲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親崭捍。 傳聞我的和親對象是個殘疾皇子尸折,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容