(WWDC) 理解 Swift 性能

 

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

 

内容概览

  • 内存分配
  • 引用计数
  • 方法分发
  • 协议类型
  • 泛型代码
  • 总结

 

1306450-5826e9b753851707.png

 

考虑性能的维度


 


 

 

内存分配

 

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

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

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

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

 

内存分配示例



使用 struct

1306450-57db6221810e9a6d.png

定义 Point 并使用

1306450-a26595068524932a.png

为 point1 和 point2 分配栈内存

1306450-08c4e8847161c885.png

将 Point 赋值给 point1

1306450-f4f228bd5eae70c6.png

将 point1 赋值给 point2

1306450-20b9e98a410d8325.png

更改 point2 的值

1306450-4874866af56bf097.png

执行结束,回收内存



使用 class

1306450-ab8ae85062a35385.png

将 struct 改为 class

1306450-9c2405f2df0f1159.png

为 point1 和 point2 分配栈内存

1306450-28ab038953c984ec.png

在堆上为 Point 分配内存

1306450-25f6025511fb9676.png

让 point1 指向 Point 实例

1306450-c63c392000876e69.png

将 point2 指向 point1 指向的 Point 实例

1306450-d40ffe2f187f958d.png

更改 point2 指向的 Point 实例的值

1306450-b740a794d9db43f8.png

释放 Point 实例的堆内存

1306450-ac13b5f5e7785ef5.png

释放 point1 和 point2 的栈内存



使用 struct 优化内存分配

1306450-1335b3f66d2f5c71.png

将 String 作为 Key

请注意,String中的字符(Character)存储在堆内存中!

 

1306450-6ff61b429aea141f.png

定义 struct 作为 Key

这里,使用 struct 保证内存分配在栈上进行,使代码更安全也更高效!


 


 

 

引用计数

 

引用计数除了加减操作,还有以下操作:

  • 间接操作
  • 线程安全

1306450-430e8d4616bf6544.png

Swift 伪代码

为了执行 retain, release 操作,Swift 需要在堆内存中进行引用计数。

1306450-47ac8dbc669d70e5.png

refCount 增加

1306450-3b15c6f4cb972934.png

refCount 增加

1306450-180b77c480ca0a0f.png

refCount 减少

1306450-3c86b30395b8082e.png

refCount 减少

1306450-782fdf5e6657713c.png

释放堆内存

 

优化引用计数的示例

1306450-7b5fb29b5c76ec63.png

String 类型需要使用堆内存

1306450-c7a32084e7798acf.png

将 uuid 定义为 UUID 值类型

1306450-65b47fdaddd000bb.png

将 mimeType 定义为 MimeType 枚举类型


 


 

 

方法分发

 

方法分发分为两种:

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

 

内联优化示例

1306450-6fbe493e2db89ab6.png

使用 struct

1306450-9a5c54ddef459291.png

调用部分被直接替换为函数的实现部分

以上优化有效地避免了两个函数的栈内存分配。

 

基于继承的多态

1306450-a20c366d4552429a.png

定义继承结构

1306450-15a71a4d7968c489.png

运行时的内存布局

1306450-39eb5b1deb90b19b.png

在函数表(V-Table)查找函数的实现部分

至此,我们可以看到明显的性能消耗差异。


 


 

 

协议类型

 

没有继承和引用的多态

1306450-5e287f07f6609cb9.png

使用 protocol 和 struct

1306450-78ecdb371cc46471.png

Swift 使用 Protocol Witness Table 进行协议的方法分发

每个实现协议的类型都有一个 Protocol Witness Table (PWT),在这个表里可以找到函数的实现部分。
Swift 使用了一种特殊的内存布局 Existential Container 来存储实现了协议的不同类型。

1306450-5cb7cdaa6f828940.png

Existential Container

Existential Container 中的前3个字(word)是 valueBuffer。

1306450-133f028a5bb38499.png

Existential Container

像 Point 这种小类型,只需要占用2个字。

1306450-c231a52ad482550a.png

Existential Container

而 Line 需要4个字,Swift 使用堆内存存储值,然后在 Existential Container 中存储值的指针。

因为 Point 和 Line 类型的差异性,Existential Container 需要使用 Value Witness Table 来管理这些值的生命周期,每个类型有一个 Value Witness Table。

1306450-05e7b4653a966680.png

Value Witness Table

1306450-c9d7bc577bfe426e.png

关系示意图

1306450-14781a9550d1aa05.png

Existential Container 引用 VWT

1306450-cd4fa747004eb4cc.png

Existential Container 引用 PWT


 

1306450-7dbd971a6cf47fdd.png

被展开的执行过程(伪代码)

 

如果感兴趣,建议下载 官方PDF 查看详细流程。



使用间接存储来优化大的值类型:

1306450-1e22d69aaff8b66f.png

 


 


 

 

泛型代码

 

1306450-f3aec8158177c583.png

采用泛型的示例代码



泛型代码的特征:

  • 静态的多态: 在调用的地方使用具体的类型
  • 每种类型都有单独的调用上下文
  • 随着调用链替换类型

1306450-a8f28554ba7a4c87.png

泛型代码的类型替换



泛型方法的实现:

  • 共享的实现
  • 使用 Protocol/Value Witness Table
  • 每种类型都有单独的调用上下文:传递 Table

1306450-c18744201a953c7d.png

1306450-41aebefe96ff16be.png



泛型特殊化:

  • 静态的多态: 在调用的地方使用具体的类型

1306450-f6f38d794c8202db.png

  • 创建类型专属版本的方法

1306450-7ed83647a1964248.png

  • 每个类型都有自己专属的方法

1306450-5c57a42ce3dc9c73.png

  • 可以进行压缩优化

1306450-bf6baae8295ada33.png

1306450-a119ad1698b7dc99.png

1306450-7138b9712aaf71d8.png



何时进行特殊化?

  • 在调用的地方
  • 定义是可见的

开启 Whole Module Optimization 可以获得更多优化的机会。


 


 

 

总结

 

使用合适的抽象,以降低动态运行时的消耗

  • 使用值类型:struct
  • 引用类型: 特性 或 面向对象风格的多态
  • 泛型:静态的多态
  • 协议:动态的多态

使用间接存储解决大的值类型的引用问题


 



参考内容:
Understanding Swift Performance

 




转载请注明出处,谢谢~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值