WWDC 2016 416

WWDC 慢慢補

補了半個去年 WWDC 影片,提到一些有關於 swift performance,主要影響的部分分別為 Allocation, Reference Count, Method Dispatch

Allocation

應該是影響效能最重的部分,以軟體來講大概是指宣告成區域變數或全域變數

1
2
3
4
func nothing() {
let c = Class() // 全域變數
let s = Struct() // 區域變數
}

更精確地講的話是指你的變數是存放在 stack 還是 heap 中,放在 heap 的話就如同 c langmemory allocation 去申請一塊記憶體位置,不用的時候還得 free 掉,還牽扯到多執行緒共用要不要 lock 等等問題。

所以 c(變數) 是個 pointer 而資料是在 heap 內,s(變數) 的話是位於 stack,應此只要移動 SP 就做完事了。


Reference Count

經歷過 ARC 的人應該會有感覺,但要有更深體會應該要體驗 MRC 才會更有感覺(開始玩已是 ARC 時代),前年的 WWDC 其實也有提道要使用 value type,用 refference type 的話 compiler 會幫你使用前加上 obj.retain() 使用後加上 obj.release()

你寫的 code
1
2
3
4
5
6
7
for c in classes {
c.do_some_thing()
}

for s in structs {
s.do_some_thing()
}
compiler 幫你補的
1
2
3
4
5
6
7
8
9
10
11
12
13
14

for c in classes {
c.retain()
c.do_some_thing()
c.release()
}

for s in structs {
s.class1.retain()
s.class2.retain()
s.do_some_thing()
s.class1.release()
s.class2.release()
}

Method Dispatch

分成 static & dynamic,基本上 class func 預設是 dynamic,影響在於 compiler 有沒有能力幫你做優化,影片例子是 function inline

class 如果要變成 static dispatch 可以使用 final class or final func

優化前
1
2
3
4
func action1() {action2()}
func action2() {action3()}
func action3() {do_some_thing()}
action1()
compiler 優化後
1
do_some_thing()

String VS Hashable

String 屬於比較特殊的 struct,他跟 class 一樣會被宣告在 heap 區,受到 alloc & free 的管理。

key:String
1
2
3
4
5
enum Color {case blue,green,gray}
enum Orientation {case left,right}
enum Tail {case none,tail,bubble}
var cache1 = [String:Int]()
// key = "\(color):\(orientation):\(tail)"

透過 Hashable 取代掉 String

key:Hashable
1
2
3
4
5
6
struct Attributes:Hashable {
var color:Color
var orientation:Orientation
var tail:Tail
}
var cache2 = [Attributes:Int]()

減少 struct 內的 class

struct 內的 class 其實也會受到 ARC 管理,使用前後也會被隱含加入 retain() & release(),去 class 可以減少 ARC 的隱含成本。

1
2
3
4
5
struct Attachment {
let fileURL:URL
let uuid:String
let mimeType:String
}
1
2
3
4
5
6
7
8
9
10
enum MimeType:String {
case jpeg = "image/jpeg"
case png = "image/jng"
case gif = "image/gif"
}
struct AttachmentDeClass {
let fileURL:URL
let uuid:UUID
let mimeType:MimeType
}

protocol VS class

官方有在推薦使用 protocol 而非 class 繼承方式,已知說 class allocationheapcall function by V-Table(啥?)

protocol + struct 真的會比 class 快?

以下分別是 protocol + struct & class 的定義 :

class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ClassDrawable {
func draw() -> Int {
return 0
}
}

class ClassLine:ClassDrawable {
let x1:Int = 0
let x2:Int = 0
let y1:Int = 0
let y2:Int = 0
override func draw() -> Int {
return x1 + y1 + x2 + y2
}
}
protocol + struct
1
2
3
4
5
6
7
8
9
10
11
12
13
protocol Drawable {
func draw() -> Int
}

struct Line:Drawable {
let x1:Int = 0
let x2:Int = 0
let y1:Int = 0
let y2:Int = 0
func draw() -> Int {
return x1 + y1 + x2 + y2
}
}

接著要提到 protocol 的實作部分,會因為你的實作甚至比 class 還慢上許多

EC (Existential Container)

影響最大的部分在於 EC,他應該是屬於 5 Word 大小的 struct

其中 Value Buffer 佔了 3 Word,且他專門負責存放 data

  • 如果你的 struct data <= 3 Worddata 可以完整的放到 Value Buffer
  • 反之你的 struct data 會被 allocate 在 heap,Value Buffer 只留下指標指過去。
    (感覺比 class 還嚴重,一樣放到 heap,還多了 EC, PWT, VWT)
Existential Container 單位
 
Value Buffer
 
 
3 Word
 
vwt: 1 Word
pwt: 1 Word
PWT (Protocol Witness Table)
Protocol Witness Table(pwt)
draw:
...:
VWT (Value Witness Table)
Value Witness Table(vwt)
allocate:
copy:
destruct:
deallocate:

Generic VS protocol

protocol
1
2
let drawable:Drawable = Line()
let _ = drawable.draw()
Generic
1
2
3
4
5
func drawing<T:Drawable>(drawable:T) -> Int {
return drawable.draw()
}
let drawable = Line()
let _ = drawing(drawable: drawable)