Swift 高级性能优化
Swift 高级性能优化
写在前面
本文中会提到很多下划线开头的声明,这些声明并不是稳定的,且错误的使用可能导致未定义行为。在使用这些特性前,请确认你知道自己在做什么。
另外,Swift 标准库以及 Apple Frameworks 中以下划线开头的声明不会出现在代码补全中,你需要自己输入完整的名称。
本文的知识大多来源于 Swift 的官方文档 UnderscoredAttributes.md 和 OptimizationTips.rst 并加上了我自己的理解和经验。阅读最新的官方原始文档可以帮助你更好地理解这些特性。
分支优化
if
语句是使用频率非常高的一个语句。在一些时候,我们能够预测到某一 if
语句进入一个分支的概率比另一个分支高,这时我们可以使用特殊语法告诉编译器如何对其进行优化。
_fastPath(_:)
使用 _fastPath(_:)
来说明进入 true
分支的可能性更高:
1 | let result = Int.random(in: 0..<10) |
_fastPath(_:)
函数本身接受一个 Bool
类型的参数,返回值类型也是 Bool
。实际上,它的返回值与输入是一致的,这个函数只是起到一个标记的作用,告诉编译器如何对分支进行优化。
当然,如果没有 else
语句,_fastPath(_:)
也能够正常工作,语义是一样的。
_slowPath(_:)
用法与 _fastPath(_:)
一样,效果与其相反。使用 _slowPath(_:)
来说明进入 false
分支的可能性更高:
1 | let result = Int.random(in: 0..<10) |
_onFastPath()
标记所在的分支被运行的可能性更高:
1 | let result = Int.random(in: 0..<10) |
为声明添加特性
特性(attribute)用于修饰声明或类型,例如 @available(...)
就是一个特性。我们这里主要使用其修饰声明以对编译器提供额外的优化信息。
@_effects(...)
系列
@_effects(...)
告诉编译器函数的实现只会产生指定的副作用,以向编译器提供不易推断的额外优化信息。
@_effects(readnone)
标记函数不会产生任何可观测到的内存读写以及其他任何副作用。
此类函数并非完全不能读写内存,可以在这些函数内对函数内部的对象进行操作。例如:
1 | (readnone) |
函数被标记为 readnone
则代表它的两次传入相同参数的调用可以被简化为一次调用。例如:
1 | let m = index(0) |
应当与下面的代码效果完全一致:
1 | let m = index(0) |
@_effects(readonly)
标记函数不会产生任何可观测到的内存写入以及除读取内存以外的其他任何副作用。
和 readnone
一样,函数也可以操作其内部的对象。
函数被标记为 readonly
代表如果它的返回值没有被使用,则可以不对此函数进行调用。
内联
通过对函数进行内联,可以消除处理函数调用时的开销。但如果函数体过大,内联会导致二进制大小增加,这是一个通过大小换取性能的操作。
@inlinable
将函数标记为 @inlinable
,函数的实现将被作为模块公开接口的一部分被公开,这允许调用方使用函数的具体实现替换函数调用。也正因此,被标记为 @inlinable
的函数不能与带有 private
、fileprivate
或是未标记为 @usableFromInline
的 internal
符号交互。
@inline(__always)
这将强制函数总是内联,除非未开启优化(-Onone
)或函数不能被内联(例如递归调用)。
@_transparent
这将在编译的早期阶段内联函数,即使是在未开启优化的构建中。优先考虑使用 @inline(__always)
而不是 @_transparent
。
写时复制
每次将值类型的实例作为参数传递或将其赋值到另一变量时,这个对象都会被完整复制一份。如果这个对象比较大,这就会是一个较大的开销。但在一些时候,即使不在内存中复制对象也不会影响程序运行,例如将值作为参数传递时,我们只读取值而不进行写入,这一份拷贝就是不必要的。使用写时复制(copy-on-write, 简称 COW)则可以解决此问题。
什么是写时复制
写时复制与其字面意思一样,只有在对象被写入时才真正地复制这个对象。例如下面的代码:
1 | let a = [0, 7, 2] |
在第二行,虽然我们定义了新的变量 b = a
,但在此时数组并不会被复制。数组会在第三行,对新的变量进行写入时才会在内存中被复制。
自行实现写时复制
可以通过一个包装器来为自己的值类型实现写时复制:
1 | final class Ref<T> { |
使用:
1 | struct Tree: P { |