美文网首页
(WWDC) 理解 Swift 性能

(WWDC) 理解 Swift 性能

作者: FicowShen | 来源:发表于2019-06-12 13:23 被阅读0次

理解如何实现才能更好地去理解性能

内容概览

  • 内存分配
  • 引用计数
  • 方法分发
  • 协议类型
  • 泛型代码
  • 总结
考虑性能的维度




内存分配

在栈上分配、回收内存时,只需要移动栈指针即可完成操作。操作的成本如同给一个整型变量赋值。

在堆上分配内存时,需要在复杂的堆数据结构中寻找未被使用的大小适合的内存。
在堆上回收内存时,需要找到合适的位置,然后才能插入内存。

很明显,在堆上操作的成本比在栈上的更高。

除此之外,多个线程可以同时在堆上分配内存,所以堆需要使用锁或其他同步机制以保证完整性。
这就导致了更严重的性能消耗。

内存分配示例



使用 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 枚举类型




方法分发

方法分发分为两种:

  • 静态分发
    1. 在运行时,直接运行方法的实现部分
    2. 可以进行内联(代码直接嵌入)或其他优化操作
  • 动态分发
    1. 在运行时,需要在函数表(V-Table)查找实现部分
    2. 找到实现部分后,才能执行
    3. 无法进行内联或其他优化操作
内联优化示例
使用 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




转载请注明出处,谢谢~

相关文章

网友评论

      本文标题:(WWDC) 理解 Swift 性能

      本文链接:https://www.haomeiwen.com/subject/yjmkfctx.html