1️⃣ 优先使用 泛型 + 值类型
- 值类型方法(struct / enum)默认静态派发
- 泛型函数在类型已知时也会被编译器 特化和内联
- 示例:
struct Vector2D {
var x, y: Double
@inline(__always)
func magnitude() -> Double {
return (x*x + y*y).squareRoot()
}
}
func computeMagnitude<T: Numeric>(_ v: Vector2D) -> Double {
return v.magnitude() // 静态派发 + 内联
}
- 泛型 T 已知 → 静态派发
- 静态派发 → 内联 + 去掉函数调用开销
2️⃣ 类 API 优化:用 final / private / internal
- final class 或 final 方法 → 静态派发
- private/internal 方法 → 编译器可静态调用
- 避免 open / public 非 final 方法在热路径被动态派发
final class HotPath {
@inline(__always)
func compute() { ... } // 静态派发
}
- 原则:能 final 的都 final,能 private 的都 private → 给编译器确定性
3️⃣ 避免协议存在类型(existential)在热路径
-
协议类型变量 → witness table 动态派发 → 每次调用都有间接开销
-
解决方法:
- 热路径用 泛型约束
- 大量数据使用 struct/泛型集合,不存协议类型
-
示例:
protocol Shape { func area() -> Double }
func totalArea<T: Shape>(_ shapes: [T]) -> Double { // 泛型 T
return shapes.reduce(0) { $0 + $1.area() } // 静态派发
}
- 如果改成
[Shape](protocol existential) → 每次调用area()都是动态派发
4️⃣ 使用 @inlinable / @inline(__always) 控制内联
-
热路径函数:
- 小函数 →
@inline(__always)强制内联 - 跨模块泛型 →
@inlinable允许调用方特化
- 小函数 →
@inlinable
func double<T: Numeric>(_ x: T) -> T { x + x }
-
内联后:
- 去掉函数调用
- 泛型特化可消除 CoW 和间接开销
5️⃣ 拆分默认实现与核心热路径
- 协议扩展默认实现 → 静态派发,不要在热路径依赖多态
- 核心热路径 → 泛型/struct → 静态派发
- 默认实现只提供 fallback
protocol P { func compute() }
extension P {
func compute() { /* default, static */ }
}
struct S: P {
func compute() { /* hot path, static dispatch */ }
}
- 避免使用
[P]在循环中调用compute()
6️⃣ 批量操作 / inout
-
对大型 struct/数组:
- 使用 inout 或 局部副本 → 减少 CoW 检查
-
批量修改 → 减少动态派发和堆分配
func scaleAll<T: Numeric>(_ values: inout [T], factor: T) {
for i in 0..<values.count { values[i] *= factor }
}
- 静态派发 + 内联 → 高效
7️⃣ 小结:设计原则
| 目标 | 实践方法 |
|---|---|
| 静态派发 | struct / enum / final class / final 方法 / private/internal |
| 避免协议动态派发 | 泛型约束代替 protocol existential |
| 内联优化 | @inline(__always) 小函数 / @inlinable 跨模块泛型 |
| CoW 优化 | inout 参数 / 局部副本 / 批量操作 |
| 多态可控 | 协议扩展默认实现提供 fallback,核心热路径显式实现 |
💡 核心理念:
热路径尽量用值类型 + 泛型 + final/private + 内联 + inout → 编译器可静态派发 → 避免动态开销
协议存在类型、跨模块非 final 方法 → 动态派发 → 在非热点或接口层使用