理解如何实现才能更好地去理解性能
内容概览
- 内存分配
- 引用计数
- 方法分发
- 协议类型
- 泛型代码
- 总结
考虑性能的维度
内存分配
在栈上分配、回收内存时,只需要移动栈指针即可完成操作。操作的成本如同给一个整型变量赋值。
在堆上分配内存时,需要在复杂的堆数据结构中寻找未被使用的大小适合的内存。
在堆上回收内存时,需要找到合适的位置,然后才能插入内存。
很明显,在堆上操作的成本比在栈上的更高。
除此之外,多个线程可以同时在堆上分配内存,所以堆需要使用锁或其他同步机制以保证完整性。
这就导致了更严重的性能消耗。
内存分配示例
使用 struct
定义 Point 并使用
为 point1 和 point2 分配栈内存
将 Point 赋值给 point1
将 point1 赋值给 point2
更改 point2 的值
执行结束,回收内存
使用 class
将 struct 改为 class
为 point1 和 point2 分配栈内存
在堆上为 Point 分配内存
让 point1 指向 Point 实例
将 point2 指向 point1 指向的 Point 实例
更改 point2 指向的 Point 实例的值
释放 Point 实例的堆内存
释放 point1 和 point2 的栈内存
使用 struct 优化内存分配
将 String 作为 Key
请注意,String中的字符(Character)存储在堆内存中!
定义 struct 作为 Key
这里,使用 struct 保证内存分配在栈上进行,使代码更安全也更高效!
引用计数
引用计数除了加减操作,还有以下操作:
- 间接操作
- 线程安全
Swift 伪代码
为了执行 retain, release 操作,Swift 需要在堆内存中进行引用计数。
refCount 增加
refCount 增加
refCount 减少
refCount 减少
释放堆内存
优化引用计数的示例
String 类型需要使用堆内存
将 uuid 定义为 UUID 值类型
将 mimeType 定义为 MimeType 枚举类型
方法分发
方法分发分为两种:
- 静态分发
- 在运行时,直接运行方法的实现部分
- 可以进行内联(代码直接嵌入)或其他优化操作
- 动态分发
- 在运行时,需要在函数表(V-Table)查找实现部分
- 找到实现部分后,才能执行
- 无法进行内联或其他优化操作
内联优化示例
使用 struct
调用部分被直接替换为函数的实现部分
以上优化有效地避免了两个函数的栈内存分配。
基于继承的多态
定义继承结构
运行时的内存布局
在函数表(V-Table)查找函数的实现部分
至此,我们可以看到明显的性能消耗差异。
协议类型
没有继承和引用的多态
使用 protocol 和 struct
Swift 使用 Protocol Witness Table 进行协议的方法分发
每个实现协议的类型都有一个 Protocol Witness Table (PWT),在这个表里可以找到函数的实现部分。
Swift 使用了一种特殊的内存布局 Existential Container 来存储实现了协议的不同类型。
Existential Container
Existential Container 中的前3个字(word)是 valueBuffer。
Existential Container
像 Point 这种小类型,只需要占用2个字。
Existential Container
而 Line 需要4个字,Swift 使用堆内存存储值,然后在 Existential Container 中存储值的指针。
因为 Point 和 Line 类型的差异性,Existential Container 需要使用 Value Witness Table 来管理这些值的生命周期,每个类型有一个 Value Witness Table。
Value Witness Table
关系示意图
Existential Container 引用 VWT
Existential Container 引用 PWT
被展开的执行过程(伪代码)
如果感兴趣,建议下载 官方PDF 查看详细流程。
使用间接存储来优化大的值类型:
泛型代码
采用泛型的示例代码
泛型代码的特征:
- 静态的多态: 在调用的地方使用具体的类型
- 每种类型都有单独的调用上下文
- 随着调用链替换类型
泛型代码的类型替换
泛型方法的实现:
- 共享的实现
- 使用 Protocol/Value Witness Table
- 每种类型都有单独的调用上下文:传递 Table
泛型特殊化:
- 静态的多态: 在调用的地方使用具体的类型
- 创建类型专属版本的方法
- 每个类型都有自己专属的方法
- 可以进行压缩优化
何时进行特殊化?
- 在调用的地方
- 定义是可见的
开启 Whole Module Optimization 可以获得更多优化的机会。
总结
使用合适的抽象,以降低动态运行时的消耗
- 使用值类型:struct
- 引用类型: 特性 或 面向对象风格的多态
- 泛型:静态的多态
- 协议:动态的多态
使用间接存储解决大的值类型的引用问题
参考内容:
Understanding Swift Performance
转载请注明出处,谢谢~