Swift 协议的性能

当我们通过协议类型来创建一个变量时,这个变量会被包装到一个叫做存在容器的盒子里,这个存在容器除了变量本身的数据外,还会存储一些其它的数据,因此它占用的内存会变大,相应的也会损失些性能。

一个协议类型,因为需要同时支持类、结构体、枚举,同时还要支持动态派发,因此协议内部需要维持这些类型、方法的相关数据。一个普通的协议的内部构造大致如下图:
屏幕快照 2017-02-15 21.06.43

协议内部会有一个存储值的缓冲区,一些元数据,还有若干个目击表。这些信息都需要占用一定的内存,因此协议类型所占用的内存要比具体的类、结构体、枚举等类型更大。比如下面这段代码,有一个Animal协议,一个Person结构体遵循协议,各有两个方法接受一个遵循Animal的变量作为参数,并返回这个参数的大小。其中第一个方法将协议当作泛型约束,第二个方法将协议当作独立类型来使用,其结果是第一个方法接受参数的大小为0,而第二个方法接受的参数大小达到了40。原因就是通过协议接受一个参数是,会将这个参数进行包装,添加上图中的信息,导致内存变大。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protocol Animal {}
struct Person: Animal {}

let animal = Person()

func f<C: Animal>(_ x: C) -> Int {
return MemoryLayout.size(ofValue: x)
}

func g(_ x: Animal) -> Int {
return MemoryLayout.size(ofValue: x)
}

f(animal) // 0
g(animal) // 40

除了普通的协议外,Swift中还有一种只用于类的协议,这种协议因为只用于类,不需要保存结构体、枚举等信息,因此占用的内存会比一般的协议小。看下面的代码,这时协议包装的参数大小只有16了。因此,如果一个协议只有类才使用,不妨带上: class,能带来一定的性能优化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protocol Animal: class {}
class Person: Animal {}

let animal = Person()

func f<C: Animal>(_ x: C) -> Int {
return MemoryLayout.size(ofValue: x)
}

func g(_ x: Animal) -> Int {
return MemoryLayout.size(ofValue: x)
}

f(animal) // 8
g(animal) // 16

综上所述,普通的协议,因为需要支持的类型比较多,需要维护的数据较多,一方面导致占用内存变大,另一方面也会阻止编译器的优化,导致带来一定的性能损耗。因此如果要提高协议的性能,有两个方法,一个是对于只有类才用得上的协议使用类专用协议,另一个就是将协议作为泛型约束而不是独立来使用。

过早的优化的是万恶之源,一般并不太需要注意协议带来的性能损耗,除非这真的成了应用的性能瓶颈。

坚持原创技术分享,您的支持将鼓励我继续创作!