5-19.【性能分析与优化】在性能敏感路径中,你会如何设计 API 来避免动态派发?

4 阅读2分钟

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 classfinal 方法 → 静态派发
  • private/internal 方法 → 编译器可静态调用
  • 避免 open / public 非 final 方法在热路径被动态派发
final class HotPath {
    @inline(__always)
    func compute() { ... }  // 静态派发
}
  • 原则:能 final 的都 final,能 private 的都 private → 给编译器确定性

3️⃣ 避免协议存在类型(existential)在热路径

  • 协议类型变量 → witness table 动态派发 → 每次调用都有间接开销

  • 解决方法

    1. 热路径用 泛型约束
    2. 大量数据使用 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 方法 → 动态派发 → 在非热点或接口层使用