真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

【基本功】深入剖析Swift性能優(yōu)化-創(chuàng)新互聯(lián)

美美今天請(qǐng)來了我們技術(shù)團(tuán)隊(duì)很厲害的iOS女神亞男小姐姐深度剖析Swift,她特別講解了如何才能開發(fā)出高性能的Swift程序。希望對(duì)你有所幫助哦~Enjoy Reading!

創(chuàng)新互聯(lián)專注于天橋網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠為您提供天橋營銷型網(wǎng)站建設(shè),天橋網(wǎng)站制作、天橋網(wǎng)頁設(shè)計(jì)、天橋網(wǎng)站官網(wǎng)定制、微信小程序服務(wù),打造天橋網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供天橋網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。

簡介

2014年,蘋果公司在WWDC上發(fā)布Swift這一新的編程語言。經(jīng)過幾年的發(fā)展,Swift已經(jīng)成為iOS開發(fā)語言的“中流砥柱”,Swift提供了非常靈活的高級(jí)別特性,例如協(xié)議、閉包、泛型等,并且Swift還進(jìn)一步開發(fā)了強(qiáng)大的SIL(Swift Intermediate Language)用于對(duì)編譯器進(jìn)行優(yōu)化,使得Swift相比Objective-C運(yùn)行更快性能更優(yōu),Swift內(nèi)部如何實(shí)現(xiàn)性能的優(yōu)化,我們本文就進(jìn)行一下解讀,希望能對(duì)大家有所啟發(fā)和幫助。

針對(duì)Swift性能提升這一問題,我們可以從概念上拆分為兩個(gè)部分:

  1. 編譯器:Swift編譯器進(jìn)行的性能優(yōu)化,從階段分為編譯期和運(yùn)行期,內(nèi)容分為時(shí)間優(yōu)化和空間優(yōu)化。

  2. 開發(fā)者:通過使用合適的數(shù)據(jù)結(jié)構(gòu)和關(guān)鍵字,幫助編譯器獲取更多信息,進(jìn)行優(yōu)化。

下面我們將從這兩個(gè)角度切入,對(duì)Swift性能優(yōu)化進(jìn)行分析。通過了解編譯器對(duì)不同數(shù)據(jù)結(jié)構(gòu)處理的內(nèi)部實(shí)現(xiàn),來選擇最合適的算法機(jī)制,并利用編譯器的優(yōu)化特性,編寫高性能的程序。

理解Swift的性能

理解Swift的性能,首先要清楚Swift的數(shù)據(jù)結(jié)構(gòu),組件關(guān)系和編譯運(yùn)行方式。

  • 數(shù)據(jù)結(jié)構(gòu)

    Swift的數(shù)據(jù)結(jié)構(gòu)可以大體拆分為:Class,Struct,Enum。

  • 組件關(guān)系

    組件關(guān)系可以分為:inheritance,protocols,generics。

  • 方法分派方式

    方法分派方式可以分為Static dispatch和Dynamic dispatch。

要在開發(fā)中提高Swift性能,需要開發(fā)者去了解這幾種數(shù)據(jù)結(jié)構(gòu)和組件關(guān)系以及它們的內(nèi)部實(shí)現(xiàn),從而通過選擇最合適的抽象機(jī)制來提升性能。

首先我們對(duì)于性能標(biāo)準(zhǔn)進(jìn)行一個(gè)概念陳述,性能標(biāo)準(zhǔn)涵蓋三個(gè)標(biāo)準(zhǔn):

  • Allocation

  • Reference counting

  • Method dispatch

接下來,我們會(huì)分別對(duì)這幾個(gè)指標(biāo)進(jìn)行說明。

Allocation

內(nèi)存分配可以分為堆區(qū)棧區(qū),在棧的內(nèi)存分配速度要高于堆,結(jié)構(gòu)體和類在堆棧分配是不同的。

Stack

基本數(shù)據(jù)類型和結(jié)構(gòu)體默認(rèn)在棧區(qū),棧區(qū)內(nèi)存是連續(xù)的,通過出棧入棧進(jìn)行分配和銷毀,速度很快,高于堆區(qū)。

我們通過一些例子進(jìn)行說明:

//示例 1
// Allocation
// Struct
struct Point {
 var x, y:Double
 func draw() { … }
}
let point1 = Point(x:0, y:0) //進(jìn)行point1初始化,開辟棧內(nèi)存
var point2 = point1 //初始化point2,拷貝point1內(nèi)容,開辟新內(nèi)存
point2.x = 5 //對(duì)point2的操作不會(huì)影響point1
// use `point1`
// use `point2`

以上結(jié)構(gòu)體的內(nèi)存是在棧區(qū)分配的,內(nèi)部的變量也是內(nèi)聯(lián)在棧區(qū)。將point1賦值給point2實(shí)際操作是在棧區(qū)進(jìn)行了一份拷貝,產(chǎn)生了新的內(nèi)存消耗point2,這使得point1point2是完全獨(dú)立的兩個(gè)實(shí)例,它們之間的操作互不影響。在使用point1point2之后,會(huì)進(jìn)行銷毀。

Heap

高級(jí)的數(shù)據(jù)結(jié)構(gòu),比如類,分配在堆區(qū)。初始化時(shí)查找沒有使用的內(nèi)存塊,銷毀時(shí)再從內(nèi)存塊中清除。因?yàn)槎褏^(qū)可能存在多線程的操作問題,為了保證線程安全,需要進(jìn)行加鎖操作,因此也是一種性能消耗。

// Allocation
// Class
class Point {
 var x, y:Double
 func draw() { … }
}
let point1 = Point(x:0, y:0) //在堆區(qū)分配內(nèi)存,棧區(qū)只是存儲(chǔ)地址指針
let point2 = point1 //不產(chǎn)生新的實(shí)例,而是對(duì)point2增加對(duì)堆區(qū)內(nèi)存引用的指針
point2.x = 5 //因?yàn)閜oint1和point2是一個(gè)實(shí)例,所以point1的值也會(huì)被修改
// use `point1`
// use `point2`

以上我們初始化了一個(gè)Class類型,在棧區(qū)分配一塊內(nèi)存,但是和結(jié)構(gòu)體直接在棧內(nèi)存儲(chǔ)數(shù)值不同,我們只在棧區(qū)存儲(chǔ)了對(duì)象的指針,指針指向的對(duì)象的內(nèi)存是分配在堆區(qū)的。需要注意的是,為了管理對(duì)象內(nèi)存,在堆區(qū)初始化時(shí),除了分配屬性內(nèi)存(這里是Double類型的x,y),還會(huì)有額外的兩個(gè)字段,分別是typerefCount,這個(gè)包含了type,refCount和實(shí)際屬性的結(jié)構(gòu)被稱為blue box。

內(nèi)存分配總結(jié)

從初始化角度,Class相比Struct需要在堆區(qū)分配內(nèi)存,進(jìn)行內(nèi)存管理,使用了指針,有更強(qiáng)大的特性,但是性能較低。

優(yōu)化方式:

對(duì)于頻繁操作(比如通信軟件的內(nèi)容氣泡展示),盡量使用Struct替代Class,因?yàn)闂?nèi)存分配更快,更安全,操作更快。

Reference counting

Swift通過引用計(jì)數(shù)管理堆對(duì)象內(nèi)存,當(dāng)引用計(jì)數(shù)為0時(shí),Swift確認(rèn)沒有對(duì)象再引用該內(nèi)存,所以將內(nèi)存釋放。

對(duì)于引用計(jì)數(shù)的管理是一個(gè)非常高頻的間接操作,并且需要考慮線程安全,使得引用計(jì)數(shù)的操作需要較高的性能消耗。

對(duì)于基本數(shù)據(jù)類型的Struct來說,沒有堆內(nèi)存分配和引用計(jì)數(shù)的管理,性能更高更安全,但是對(duì)于復(fù)雜的結(jié)構(gòu)體,如:

// Reference Counting
// Struct containing references
struct Label {
 var text:String
 var font:UIFont
 func draw() { … }
}
let label1 = Label(text:"Hi", font:font)  //棧區(qū)包含了存儲(chǔ)在堆區(qū)的指針
let label2 = label1 //label2產(chǎn)生新的指針,和label1一樣指向同樣的string和font地址
// use `label1`
// use `label2`

這里看到,包含了引用的結(jié)構(gòu)體相比Class,需要管理雙倍的引用計(jì)數(shù)。每次將結(jié)構(gòu)體作為參數(shù)傳遞給方法或者進(jìn)行直接拷貝時(shí),都會(huì)出現(xiàn)多份引用計(jì)數(shù)。下圖可以比較直觀的理解:

【基本功】深入剖析Swift性能優(yōu)化

備注:包含引用類型的結(jié)構(gòu)體出現(xiàn)Copy的處理方式

Class在拷貝時(shí)的處理方式:

【基本功】深入剖析Swift性能優(yōu)化

引用計(jì)數(shù)總結(jié)

  • Class在堆區(qū)分配內(nèi)存,需要使用引用計(jì)數(shù)器進(jìn)行內(nèi)存管理。

  • 基本類型的Struct在棧區(qū)分配內(nèi)存,無引用計(jì)數(shù)管理。

  • 包含強(qiáng)類型的Struct通過指針管理在堆區(qū)的屬性,對(duì)結(jié)構(gòu)體的拷貝會(huì)創(chuàng)建新的棧內(nèi)存,創(chuàng)建多份引用的指針,Class只會(huì)有一份。

優(yōu)化方式

在使用結(jié)構(gòu)體時(shí):

  1. 通過使用精確類型,例如UUID替代String(UUID字節(jié)長度固定128字節(jié),而不是String任意長度),這樣就可以進(jìn)行內(nèi)存內(nèi)聯(lián),在棧內(nèi)存儲(chǔ)UUID,我們知道,棧內(nèi)存管理更快更安全,并且不需要引用計(jì)數(shù)。

  2. Enum替代String,在棧內(nèi)管理內(nèi)存,無引用計(jì)數(shù),并且從語法上對(duì)于開發(fā)者更友好。

Method Dispatch

我們之前在Static dispatch VS Dynamic dispatch中提到過,能夠在編譯期確定執(zhí)行方法的方式叫做靜態(tài)分派Static dispatch,無法在編譯期確定,只能在運(yùn)行時(shí)去確定執(zhí)行方法的分派方式叫做動(dòng)態(tài)分派Dynamic dispatch。

Static dispatch更快,而且靜態(tài)分派可以進(jìn)行內(nèi)聯(lián)等進(jìn)一步的優(yōu)化,使得執(zhí)行更快速,性能更高。

但是對(duì)于多態(tài)的情況,我們不能在編譯期確定最終的類型,這里就用到了Dynamic dispatch動(dòng)態(tài)分派。動(dòng)態(tài)分派的實(shí)現(xiàn)是,每種類型都會(huì)創(chuàng)建一張表,表內(nèi)是一個(gè)包含了方法指針的數(shù)組。動(dòng)態(tài)分派更靈活,但是因?yàn)橛胁楸砗吞D(zhuǎn)的操作,并且因?yàn)楹芏嗵攸c(diǎn)對(duì)于編譯器來說并不明確,所以相當(dāng)于block了編譯器的一些后期優(yōu)化。所以速度慢于Static dispatch

下面看一段多態(tài)代碼,以及分析實(shí)現(xiàn)方式:

//引用語義實(shí)現(xiàn)的多態(tài)
class Drawable { func draw() {} }
class Point :Drawable {
 var x, y:Double
 override func draw() { … }
}
class Line :Drawable {
 var x1, y1, x2, y2:Double
 override func draw() { … }
}
var drawables:[Drawable]
for d in drawables {
 d.draw()
}

Method Dispatch總結(jié)

Class默認(rèn)使用Dynamic dispatch,因?yàn)樵诰幾g期幾乎每個(gè)環(huán)節(jié)的信息都無法確定,所以阻礙了編譯器的優(yōu)化,比如inlinewhole module inline

使用Static dispatch代替Dynamic dispatch提升性能

我們知道Static dispatch快于Dynamic dispatch,如何在開發(fā)中去盡可能使用Static dispatch。

  • inheritance constraints繼承約束
    我們可以使用final關(guān)鍵字去修飾Class,以此生成的Final class,使用Static dispatch

  • access control訪問控制
    private關(guān)鍵字修飾,使得方法或?qū)傩灾粚?duì)當(dāng)前類可見。編譯器會(huì)對(duì)方法進(jìn)行Static dispatch。

編譯器可以通過whole module optimization檢查繼承關(guān)系,對(duì)某些沒有標(biāo)記final的類通過計(jì)算,如果能在編譯期確定執(zhí)行的方法,則使用Static dispatchStruct默認(rèn)使用Static dispatch。

Swift快于OC的一個(gè)關(guān)鍵是可以消解動(dòng)態(tài)分派。

總結(jié)

Swift提供了更靈活的Struct,用以在內(nèi)存、引用計(jì)數(shù)、方法分派等角度去進(jìn)行性能的優(yōu)化,在正確的時(shí)機(jī)選擇正確的數(shù)據(jù)結(jié)構(gòu),可以使我們的代碼性能更快更安全。

延伸

你可能會(huì)問Struct如何實(shí)現(xiàn)多態(tài)呢?答案是protocol oriented programming。

以上分析了影響性能的幾個(gè)標(biāo)準(zhǔn),那么不同的算法機(jī)制Class,Protocol TypesGeneric code,它們?cè)谶@三方面的表現(xiàn)如何,Protocol TypeGeneric code分別是怎么實(shí)現(xiàn)的呢?我們帶著這個(gè)問題看下去。

Protocol Type

這里我們會(huì)討論P(yáng)rotocol Type如何存儲(chǔ)和拷貝變量,以及方法分派是如何實(shí)現(xiàn)的。不通過繼承或者引用語義的多態(tài):

protocol Drawable { func draw() }
struct Point :Drawable {
 var x, y:Double
 func draw() { … }
}
struct Line :Drawable {
 var x1, y1, x2, y2:Double
 func draw() { … }
}

var drawables:[Drawable] //遵守了Drawable協(xié)議的類型集合,可能是point或者line
for d in drawables {
 d.draw()
}

以上通過Protocol Type實(shí)現(xiàn)多態(tài),幾個(gè)類之間沒有繼承關(guān)系,故不能按照慣例借助V-Table實(shí)現(xiàn)動(dòng)態(tài)分派。

如果想了解Vtable和Witness table實(shí)現(xiàn),可以進(jìn)行點(diǎn)擊查看,這里不做細(xì)節(jié)說明。
因?yàn)镻oint和Line的尺寸不同,數(shù)組存儲(chǔ)數(shù)據(jù)實(shí)現(xiàn)一致性存儲(chǔ),使用了Existential Container。查找正確的執(zhí)行方法則使用了 Protoloc Witness Table

【基本功】深入剖析Swift性能優(yōu)化

Existential Container

Existential Container是一種特殊的內(nèi)存布局方式,用于管理遵守了相同協(xié)議的數(shù)據(jù)類型Protocol Type,這些數(shù)據(jù)類型因?yàn)椴还蚕硗焕^承關(guān)系(這是V-Table實(shí)現(xiàn)的前提),并且內(nèi)存空間尺寸不同,使用Existential Container進(jìn)行管理,使其具有存儲(chǔ)的一致性。

【基本功】深入剖析Swift性能優(yōu)化

結(jié)構(gòu)如下:

  • 三個(gè)詞大小的valueBuffer
    這里介紹一下valueBuffer結(jié)構(gòu),valueBuffer有三個(gè)詞,每個(gè)詞包含8個(gè)字節(jié),存儲(chǔ)的可能是值,也可能是對(duì)象的指針。對(duì)于small value(空間小于valueBuffer),直接存儲(chǔ)在valueBuffer的地址內(nèi), inline valueBuffer,無額外堆內(nèi)存初始化。當(dāng)值的數(shù)量大于3個(gè)屬性即large value,或者總尺寸超過valueBuffer的占位,就會(huì)在堆區(qū)開辟內(nèi)存,將其存儲(chǔ)在堆區(qū),valueBuffer存儲(chǔ)內(nèi)存指針。

  • value witness table的引用
    因?yàn)?code>Protocol Type的類型不同,內(nèi)存空間,初始化方法等都不相同,為了對(duì)Protocol Type生命周期進(jìn)行專項(xiàng)管理,用到了Value Witness Table

  • protocol witness table的引用
    管理Protocol Type的方法分派。

內(nèi)存分布如下:

1. payload_data_0 = 0x0000000000000004,
2. payload_data_1 = 0x0000000000000000,
3. payload_data_2 = 0x0000000000000000,
4. instance_type = 0x000000010d6dc408 ExistentialContainers`type    
       metadata for ExistentialContainers.Car,
5. protocol_witness_0 = 0x000000010d6dc1c0 
       ExistentialContainers protocol witness table for 
       ExistentialContainers.Car:ExistentialContainers.Drivable 
       in ExistentialContainers

Protocol Witness Table(PWT)

為了實(shí)現(xiàn)Class多態(tài)也就是引用語義多態(tài),需要V-Table來實(shí)現(xiàn),但是V-Table的前提是具有同一個(gè)父類即共享相同的繼承關(guān)系,但是對(duì)于Protocol Type來說,并不具備此特征,故為了支持Struct的多態(tài),需要用到protocol oriented programming機(jī)制,也就是借助Protocol Witness Table來實(shí)現(xiàn)(細(xì)節(jié)可以點(diǎn)擊Vtable和witness table實(shí)現(xiàn),每個(gè)結(jié)構(gòu)體會(huì)創(chuàng)造PWT表,內(nèi)部包含指針,指向方法具體實(shí)現(xiàn))。

【基本功】深入剖析Swift性能優(yōu)化

Value Witness Table(VWT)

用于管理任意值的初始化、拷貝、銷毀。

【基本功】深入剖析Swift性能優(yōu)化

  • Value Witness Table的結(jié)構(gòu)如上,是用于管理遵守了協(xié)議的Protocol Type實(shí)例的初始化,拷貝,內(nèi)存消減和銷毀的。

  • Value Witness TableSIL中還可以拆分為%relative_vwtable%absolute_vwtable,我們這里先不做展開。

  • Value Witness TableProtocol Witness Table通過分工,去管理Protocol Type實(shí)例的內(nèi)存管理(初始化,拷貝,銷毀)和方法調(diào)用。

我們來借助具體的示例進(jìn)行進(jìn)一步了解:

// Protocol Types
// The Existential Container in action
func drawACopy(local :Drawable) {
 local.draw()
}
let val :Drawable = Point()
drawACopy(val)

在Swift編譯器中,通過Existential Container實(shí)現(xiàn)的偽代碼如下:

// Protocol Types
// The Existential Container in action
func drawACopy(local :Drawable) {
 local.draw()
}
let val :Drawable = Point()
drawACopy(val)

//existential container的偽代碼結(jié)構(gòu)
struct ExistContDrawable {
 var valueBuffer:(Int, Int, Int)
 var vwt:ValueWitnessTable
 var pwt:DrawableProtocolWitnessTable
}

// drawACopy方法生成的偽代碼
func drawACopy(val:ExistContDrawable) { //將existential container傳入
 var local = ExistContDrawable()  //初始化container
 let vwt = val.vwt //獲取value witness table,用于管理生命周期
 let pwt = val.pwt //獲取protocol witness table,用于進(jìn)行方法分派
 local.type = type 
 local.pwt = pwt
 vwt.allocateBufferAndCopyValue(&local, val)  //vwt進(jìn)行生命周期管理,初始化或者拷貝
 pwt.draw(vwt.projectBuffer(&local)) //pwt查找方法,這里說一下projectBuffer,因?yàn)椴煌愋驮趦?nèi)存中是不同的(small value內(nèi)聯(lián)在棧內(nèi),large value初始化在堆內(nèi),棧持有指針),所以方法的確定也是和類型相關(guān)的,我們知道,查找方法時(shí)是通過當(dāng)前對(duì)象的地址,通過一定的位移去查找方法地址。
 vwt.destructAndDeallocateBuffer(temp) //vwt進(jìn)行生命周期管理,銷毀內(nèi)存
}


Protocol Type 存儲(chǔ)屬性

我們知道,Swift中Class的實(shí)例和屬性都存儲(chǔ)在堆區(qū),Struct實(shí)例在棧區(qū),如果包含指針屬性則存儲(chǔ)在堆區(qū),Protocol Type如何存儲(chǔ)屬性?Small Number通過Existential Container內(nèi)聯(lián)實(shí)現(xiàn),大數(shù)存在堆區(qū)。如何處理Copy呢?

Protocol大數(shù)的Copy優(yōu)化

在出現(xiàn)Copy情況時(shí):

let aLine = Line(1.0, 1.0, 1.0, 3.0)
let pair = Pair(aLine, aLine)
let copy = pair

【基本功】深入剖析Swift性能優(yōu)化

會(huì)將新的Exsitential Container的valueBuffer指向同一個(gè)value即創(chuàng)建指針引用,但是如果要改變值怎么辦?我們知道Struct值的修改和Class不同,Copy是不應(yīng)該影響原實(shí)例的值的。

這里用到了一個(gè)技術(shù)叫做Indirect Storage With Copy-On-Write,即優(yōu)先使用內(nèi)存指針。通過提高內(nèi)存指針的使用,來降低堆區(qū)內(nèi)存的初始化。降低內(nèi)存消耗。在需要修改值的時(shí)候,會(huì)先檢測(cè)引用計(jì)數(shù)檢測(cè),如果有大于1的引用計(jì)數(shù),則開辟新內(nèi)存,創(chuàng)建新的實(shí)例。在對(duì)內(nèi)容進(jìn)行變更的時(shí)候,會(huì)開啟一塊新的內(nèi)存,偽代碼如下:

class LineStorage { var x1, y1, x2, y2:Double }
struct Line :Drawable {
 var storage :LineStorage
 init() { storage = LineStorage(Point(), Point()) }
 func draw() { … }
 mutating func move() {
   if !isUniquelyReferencedNonObjc(&storage) { //如何存在多份引用,則開啟新內(nèi)存,否則直接修改
     storage = LineStorage(storage)
   }
   storage。start = ...
   }
}

這樣實(shí)現(xiàn)的目的:通過多份指針去引用同一份地址的成本遠(yuǎn)遠(yuǎn)低于開辟多份堆內(nèi)存。以下對(duì)比圖:

Protocol Type多態(tài)總結(jié)
  1. 支持Protocol Type的動(dòng)態(tài)多態(tài)(Dynamic Polymorphism)行為。

  2. 通過使用Witness Table和Existential Container來實(shí)現(xiàn)。

  3. 對(duì)于大數(shù)的拷貝可以通過Indirect Storage間接存儲(chǔ)來進(jìn)行優(yōu)化。

說到動(dòng)態(tài)多態(tài)Dynamic Polymorphism,我們就要問了,什么是靜態(tài)多態(tài)Static Polymorphism,看看下面示例:

// Drawing a copy
protocol Drawable {
 func draw()
}
func drawACopy(local :Drawable) {
 local.draw()
}

let line = Line()
drawACopy(line)
// ...
let point = Point()
drawACopy(point)

這種情況我們就可以用到泛型Generic code來實(shí)現(xiàn),進(jìn)行進(jìn)一步優(yōu)化。

泛型

我們接下來會(huì)討論泛型屬性的存儲(chǔ)方式和泛型方法是如何分派的。泛型和Protocol Type的區(qū)別在于:

  • 泛型支持的是靜態(tài)多態(tài)。

  • 每個(gè)調(diào)用上下文只有一種類型。
    查看下面的示例,foobar方法是同一種類型。

  • 在調(diào)用鏈中會(huì)通過類型降級(jí)進(jìn)行類型取代。

對(duì)于以下示例:

func foo(local :T) {
 bar(local)
}
func bar(local:T) { … }
let point = Point()
foo(point)

分析方法foobar的調(diào)用過程:

//調(diào)用過程
foo(point)-->foo(point)   //在方法執(zhí)行時(shí),Swift將泛型T綁定為調(diào)用方使用的具體類型,這里為Point
 bar(local) -->bar(local) //在調(diào)用內(nèi)部bar方法時(shí),會(huì)使用foo已經(jīng)綁定的變量類型Point,可以看到,泛型T在這里已經(jīng)被降級(jí),通過類型Point進(jìn)行取代

泛型方法調(diào)用的具體實(shí)現(xiàn)為:

  • 同一種類型的任何實(shí)例,都共享同樣的實(shí)現(xiàn),即使用同一個(gè)Protocol Witness Table。

  • 使用Protocol/Value Witness Table。

  • 每個(gè)調(diào)用上下文只有一種類型:這里沒有使用Existential Container, 而是將Protocol/Value Witness Table作為調(diào)用方的額外參數(shù)進(jìn)行傳遞。

  • 變量初始化和方法調(diào)用,都使用傳入的VWTPWT來執(zhí)行。

看到這里,我們并不覺得泛型比Protocol Type有什么更快的特性,泛型如何更快呢?靜態(tài)多態(tài)前提下可以進(jìn)行進(jìn)一步的優(yōu)化,稱為特定泛型優(yōu)化。

泛型特化

  • 靜態(tài)多態(tài):在調(diào)用站中只有一種類型
    Swift使用只有一種類型的特點(diǎn),來進(jìn)行類型降級(jí)取代。

  • 類型降級(jí)后,產(chǎn)生特定類型的方法

  • 為泛型的每個(gè)類型創(chuàng)造對(duì)應(yīng)的方法
    這時(shí)候你可能會(huì)問,那每一種類型都產(chǎn)生一個(gè)新的方法,代碼空間豈不爆炸?

  • 靜態(tài)多態(tài)下進(jìn)行特定優(yōu)化specialization
    因?yàn)槭庆o態(tài)多態(tài)。所以可以進(jìn)行很強(qiáng)大的優(yōu)化,比如進(jìn)行內(nèi)聯(lián)實(shí)現(xiàn),并且通過獲取上下文來進(jìn)行更進(jìn)一步的優(yōu)化。從而降低方法數(shù)量。優(yōu)化后可以更精確和具體。

例如:

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

從普通的泛型展開如下,因?yàn)橐С炙蓄愋偷?code>min方法,所以需要對(duì)泛型類型進(jìn)行計(jì)算,包括初始化地址、內(nèi)存分配、生命周期管理等。除了對(duì)value的操作,還要對(duì)方法進(jìn)行操作。這是一個(gè)非常的的工程。

func min(x:T, y:T, FTable:FunctionTable) -> T {
  let xCopy = FTable.copy(x)
  let yCopy = FTable.copy(y)
  let m = FTable.lessThan(yCopy, xCopy) ? y :x
  FTable.release(x)
  FTable.release(y)
  return m
}

在確定入?yún)㈩愋蜁r(shí),比如Int,編譯器可以通過泛型特化,進(jìn)行類型取代(Type Substitute),優(yōu)化為:

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

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

在使用特定優(yōu)化時(shí),調(diào)用方需要進(jìn)行類型推斷,這里需要知曉類型的上下文,例如類型的定義和內(nèi)部方法實(shí)現(xiàn)。如果調(diào)用方和類型是單獨(dú)編譯的,就無法在調(diào)用方推斷類型的內(nèi)部實(shí)行,就無法使用特定優(yōu)化,保證這些代碼一起進(jìn)行編譯,這里就用到了whole module optimization。而whole module optimization是對(duì)于調(diào)用方和被調(diào)用方的方法在不同文件時(shí),對(duì)其進(jìn)行泛型特化優(yōu)化的前提。

泛型進(jìn)一步優(yōu)化

特定泛型的進(jìn)一步優(yōu)化:

// Pairs in our program using generic types
struct Pair {
 init(_ f:T, _ s:T) {
 first = f ; second = s
 }
 var first:T
 var second:T
}
let pairOfLines = Pair(Line(), Line())
// ...

let pairOfPoint = Pair(Point(), Point())

在用到多種泛型,且確定泛型類型不會(huì)在運(yùn)行時(shí)修改時(shí),就可以對(duì)成對(duì)泛型的使用進(jìn)行進(jìn)一步優(yōu)化。

優(yōu)化的方式是將泛型的內(nèi)存分配由指針指定,變?yōu)閮?nèi)存內(nèi)聯(lián),不再有額外的堆初始化消耗。請(qǐng)注意,因?yàn)檫M(jìn)行了存儲(chǔ)內(nèi)聯(lián),已經(jīng)確定了泛型特定類型的內(nèi)存分布,泛型的內(nèi)存內(nèi)聯(lián)不能存儲(chǔ)不同類型。所以再次強(qiáng)調(diào)此種優(yōu)化只適用于在運(yùn)行時(shí)不會(huì)修改泛型類型,即不能同時(shí)支持一個(gè)方法中包含linepoint兩種類型。

whole module optimization

whole module optimization是用于Swift編譯器的優(yōu)化機(jī)制。可以通過-whole-module-optimization (或 -wmo)進(jìn)行打開。在XCode 8之后默認(rèn)打開。 Swift Package Manager在release模式默認(rèn)使用whole module optimization。

module是多個(gè)文件集合。

編譯器在對(duì)源文件進(jìn)行語法分析之后,會(huì)對(duì)其進(jìn)行優(yōu)化,生成機(jī)器碼并輸出目標(biāo)文件,之后鏈接器聯(lián)合所有的目標(biāo)文件生成共享庫或可執(zhí)行文件。

whole module optimization通過跨函數(shù)優(yōu)化,可以進(jìn)行內(nèi)聯(lián)等優(yōu)化操作,對(duì)于泛型,可以通過獲取類型的具體實(shí)現(xiàn)來進(jìn)行推斷優(yōu)化,進(jìn)行類型降級(jí)方法內(nèi)聯(lián),刪除多余方法等操作。

【基本功】深入剖析Swift性能優(yōu)化

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

  • 編譯器掌握所有方法的實(shí)現(xiàn),可以進(jìn)行內(nèi)聯(lián)泛型特化等優(yōu)化,通過計(jì)算所有方法的引用,移除多余的引用計(jì)數(shù)操作。

  • 通過知曉所有的非公共方法,如果這寫方法沒有被使用,就可以對(duì)其進(jìn)行消除。

如何降低編譯時(shí)間

和全模塊優(yōu)化相反的是文件優(yōu)化,即對(duì)單個(gè)文件進(jìn)行編譯。這樣的好處在于可以并行執(zhí)行,并且對(duì)于沒有修改的文件不會(huì)再次編譯。缺點(diǎn)在于編譯器無法獲知全貌,無法進(jìn)行深度優(yōu)化,全模塊優(yōu)化如何避免沒修改的文件再次編譯。

編譯器內(nèi)部運(yùn)行過程分為:語法分析,類型檢查,SIL優(yōu)化,LLVM后端處理。

語法分析和類型檢查一般很快,SIL優(yōu)化執(zhí)行了重要的Swift特定優(yōu)化,例如泛型特化和方法內(nèi)聯(lián)等,該過程大概占用真?zhèn)€編譯時(shí)間的三分之一。LLVM后端執(zhí)行占用了大部分的編譯時(shí)間,用于運(yùn)行降級(jí)優(yōu)化和生成代碼。

進(jìn)行全模塊優(yōu)化后,SIL優(yōu)化會(huì)將模塊再次拆分為多個(gè)部分,LLVM后端通過多線程對(duì)這些拆分模塊進(jìn)行處理,對(duì)于沒有修改的部分,不會(huì)進(jìn)行再處理。這樣就避免了修改一小部分,整個(gè)大模塊進(jìn)行LLVM后端執(zhí)行,并且多線程并行操作也會(huì)縮短處理時(shí)間。

擴(kuò)展:Swift的隱藏“Bug”

Swift因?yàn)榉椒ǚ峙蓹C(jī)制問題,所以在設(shè)計(jì)和優(yōu)化后,會(huì)產(chǎn)生和我們常規(guī)理解不太一致的結(jié)果,這當(dāng)然不能算Bug。但是還是要單獨(dú)進(jìn)行說明,避免在開發(fā)過程中,因?yàn)閷?duì)機(jī)制的掌握不足,造成預(yù)期和執(zhí)行出入導(dǎo)致的問題。

Message dispatch

我們通過上面說明結(jié)合Static dispatch VS Dynamic dispatch對(duì)方法分派方式有了了解。這里需要對(duì)Objective-C的方法分派方式進(jìn)行說明。

熟悉OC的人都知道,OC采用了運(yùn)行時(shí)機(jī)制使用obj_msgSend發(fā)送消息,runtime非常的靈活,我們不僅可以對(duì)方法調(diào)用采用swizzling,對(duì)于對(duì)象也可以通過isa-swizzling來擴(kuò)展功能,應(yīng)用場(chǎng)景有我們常用的hook和大家熟知的KVO。

大家在使用Swift進(jìn)行開發(fā)時(shí)都會(huì)問,Swift是否可以使用OC的運(yùn)行時(shí)和消息轉(zhuǎn)發(fā)機(jī)制呢?答案是可以。

Swift可以通過關(guān)鍵字dynamic對(duì)方法進(jìn)行標(biāo)記,這樣就會(huì)告訴編譯器,此方法使用的是OC的運(yùn)行時(shí)機(jī)制。

注意:我們常見的關(guān)鍵字@ObjC并不會(huì)改變Swift原有的方法分派機(jī)制,關(guān)鍵字@ObjC的作用只是告訴編譯器,該段代碼對(duì)于OC可見。

總結(jié)來說,Swift通過dynamic關(guān)鍵字的擴(kuò)展后,一共包含三種方法分派方式:Static dispatchTable dispatchMessage dispatch。下表為不同的數(shù)據(jù)結(jié)構(gòu)在不同情況下采取的分派方式:

【基本功】深入剖析Swift性能優(yōu)化

如果在開發(fā)過程中,錯(cuò)誤的混合了這幾種分派方式,就可能出現(xiàn)Bug,以下我們對(duì)這些Bug進(jìn)行分析:

SR-584

此情況是在子類的extension中重載父類方法時(shí),出現(xiàn)和預(yù)期不同的行為。

class Base:NSObject {
    var directProperty:String { return "This is Base" }
    var indirectProperty:String { return directProperty }
}

class Sub:Base { }

extension Sub {
    override var directProperty:String { return "This is Sub" }
}

執(zhí)行以下代碼,直接調(diào)用沒有問題:

Base().directProperty // “This is Base”
Sub().directProperty // “This is Sub”

間接調(diào)用結(jié)果和預(yù)期不同:

Base()。indirectProperty // “This is Base”
Sub()。indirectProperty // expected "this is Sub",but is “This is Base” <- Unexpected!

Base.directProperty前添加dynamic關(guān)鍵字就可以獲得"this is Sub"的結(jié)果。Swift在extension 文檔中說明,不能在extension中重載已經(jīng)存在的方法。

“Extensions can add new functionality to a type, but they cannot override existing functionality.”

會(huì)出現(xiàn)警告:Cannot override a non-dynamic class declaration from an extension。

【基本功】深入剖析Swift性能優(yōu)化

出現(xiàn)這個(gè)問題的原因是,NSObject的extension是使用的Message dispatch,而Initial Declaration使用的是Table dispath(查看上圖 Swift Dispatch Method)。extension重載的方法添加在了Message dispatch內(nèi),沒有修改虛函數(shù)表,虛函數(shù)表內(nèi)還是父類的方法,故會(huì)執(zhí)行父類方法。想在extension重載方法,需要標(biāo)明dynamic來使用Message dispatch。

SR-103

協(xié)議的擴(kuò)展內(nèi)實(shí)現(xiàn)的方法,無法被遵守類的子類重載:

protocol Greetable {
    func sayHi()
}
extension Greetable {
    func sayHi() {
        print("Hello")
    }
}
func greetings(greeter:Greetable) {
    greeter.sayHi()
}

現(xiàn)在定義一個(gè)遵守了協(xié)議的類Person。遵守協(xié)議類的子類LoudPerson

class Person:Greetable {
}
class LoudPerson:Person {
    func sayHi() {
        print("sub")
    }
}

執(zhí)行下面代碼結(jié)果為:

var sub:LoudPerson = LoudPerson()
sub.sayHi()  //sub

不符合預(yù)期的代碼:

var sub:Person = LoudPerson()
sub.sayHi()  //HellO  <-使用了protocol的默認(rèn)實(shí)現(xiàn)

注意,在子類LoudPerson中沒有出現(xiàn)override關(guān)鍵字。可以理解為LoudPerson并沒有成功注冊(cè)GreetableWitness table的方法。所以對(duì)于聲明為Person實(shí)際為LoudPerson的實(shí)例,會(huì)在編譯器通過Person去查找,Person沒有實(shí)現(xiàn)協(xié)議方法,則不產(chǎn)生Witness table,sayHi方法是直接調(diào)用的。解決辦法是在base類內(nèi)實(shí)現(xiàn)協(xié)議方法,無需實(shí)現(xiàn)也要提供默認(rèn)方法?;蛘邔⒒悩?biāo)記為final來避免繼承。

進(jìn)一步通過示例去理解:

// Defined protocol。
protocol A {
    func a() -> Int
}
extension A {
    func a() -> Int {
        return 0
    }
}

// A class doesn't have implement of the function。
class B:A {}

class C:B {
    func a() -> Int {
        return 1
    }
}

// A class has implement of the function。
class D:A {
    func a() -> Int {
        return 1
    }
}

class E:D {
    override func a() -> Int {
        return 2
    }
}

// Failure cases。
B().a() // 0
C().a() // 1
(C() as A).a() // 0 # We thought return 1。 

// Success cases。
D().a() // 1
(D() as A).a() // 1
E().a() // 2
(E() as A).a() // 2

其他

我們知道Class extension使用的是Static dispatch:

class MyClass {
}
extension MyClass {
    func extensionMethod() {}
}

class SubClass:MyClass {
    override func extensionMethod() {}
}

以上代碼會(huì)出現(xiàn)錯(cuò)誤,提示Declarations in extensions can not be overridden yet。

總結(jié)

  • 影響程序的性能標(biāo)準(zhǔn)有三種:初始化方式引用指針方法分派。

  • 文中對(duì)比了兩種數(shù)據(jù)結(jié)構(gòu):StructClass的在不同標(biāo)準(zhǔn)下的性能表現(xiàn)。Swift相比OC和其它語言強(qiáng)化了結(jié)構(gòu)體的能力,所以在了解以上性能表現(xiàn)的前提下,通過利用結(jié)構(gòu)體可以有效提升性能。

  • 在此基礎(chǔ)上,我們還介紹了功能強(qiáng)大的結(jié)構(gòu)體的類:Protocol TypeGeneric。并且介紹了它們?nèi)绾沃С侄鄳B(tài)以及通過使用有條件限制的泛型如何讓程序更快。

參考資料

  • swift memorylayout

  • witness table video

  • protocol types pdf

  • protocol and value oriented programming in UIKit apps  video

  • optimizing swift performance

  • whole module optimizaiton

  • increasing performance by reducing dynamic dispatch

  • protocols generics existential container

  • protocols and generics

  • why swift is swift

  • swift method dispatch

  • swift extension

  • universal dynamic dispatch for method calls

  • compiler performance.md

  • structures and classes

作者簡介

亞男,美團(tuán)點(diǎn)評(píng)iOS工程師。2017年加入美團(tuán)點(diǎn)評(píng),負(fù)責(zé)美團(tuán)管家開發(fā),研究編譯器原理。目前正積極推動(dòng)Swift組件化建設(shè)。

歡迎加入美團(tuán)iOS技術(shù)交流群,跟作者零距離交流。進(jìn)群方式:請(qǐng)加美美同學(xué)的微信(微信號(hào):MTDPtech01),回復(fù):iOS,美美會(huì)自動(dòng)拉你進(jìn)群。

----------  END  ----------

招聘信息

我們餐飲生態(tài)技術(shù)部是一個(gè)技術(shù)氛圍活躍,大牛聚集的地方。新到店緊握真正的大規(guī)模SaaS實(shí)戰(zhàn)機(jī)會(huì),多租戶、數(shù)據(jù)、安全、開放平臺(tái)等全方位的挑戰(zhàn)。業(yè)務(wù)領(lǐng)域復(fù)雜技術(shù)挑戰(zhàn)多,技術(shù)和業(yè)務(wù)能力迅速提升,最重要的是,加入我們,你將實(shí)現(xiàn)真正通過代碼來改變行業(yè)的夢(mèng)想。我們歡迎各端人才加入,Java優(yōu)先。感興趣的同學(xué)趕緊發(fā)送簡歷至 zhaoyanan02@meituan.com,我們期待你的到來。

原文鏈接:https://mp.weixin.qq.com/s/U95QmOOjeXkk-yC23cuZCQ


網(wǎng)站題目:【基本功】深入剖析Swift性能優(yōu)化-創(chuàng)新互聯(lián)
標(biāo)題鏈接:http://weahome.cn/article/dsdsod.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部