1️⃣ 函数设计:让函数小而热
- 原则:函数越小,越容易被内联
- 实践:
// 坏:大函数,不易内联
func process<T: Numeric>(_ arr: [T]) -> [T] {
var result: [T] = []
for x in arr { result.append(x * x + 1) }
return result
}
// 好:拆成小函数
@inlinable
func square<T: Numeric>(_ x: T) -> T { x * x }
@inlinable
func process<T: Numeric>(_ arr: [T]) -> [T] {
arr.map { square($0) + 1 }
}
-
好处:
square很小 → 易于内联- 调用方看到函数体 → 可生成特化版本
2️⃣ 泛型设计:类型约束明确 + 避免抽象过深
- 原则:编译器越早知道 T 的约束 → 越容易生成特化
- 实践:
// 约束明确 → 支持泛型特化
func add<T: Numeric>(_ a: T, _ b: T) -> T { a + b }
// 不够具体 → 编译器可能保留通用版本
func add<T>(_ a: T, _ b: T) -> T { ... }
-
策略:
- 明确约束(
Numeric,Comparable,FixedWidthInteger) - 泛型嵌套尽量少 → 避免“泛型套泛型”增加特化复杂度
- 明确约束(
3️⃣ 模块设计:热路径同模块,必要时用 @inlinable
- 原则:跨模块调用 → 编译器无法生成特化
- 实践:
// LibraryModule
@inlinable
public func double<T: Numeric>(_ x: T) -> T { x + x }
// AppModule
let d = double(42) // T = Int → 编译器生成特化版本
-
策略:
- 高频泛型函数放在同模块
- @inlinable 暴露实现 → 跨模块可生成特化
- 低频函数无需暴露,减少二进制膨胀
4️⃣ 调用模式:局部副本 + inout + 热路径优化
- 原则:调用方式影响 CoW 和特化
- 实践:
// 批量操作,减少 CoW
var arr = [1,2,3,4]
var result = arr // 局部副本
for i in 0..<result.count {
result[i] *= 2
}
-
策略:
- 使用
inout参数传递大型 struct → 避免多次拷贝 - 批量修改 → 减少 CoW 检查
- 热路径函数内使用小型函数 + 泛型 → 增加内联可能性
- 使用
5️⃣ 内联提示:@inlinable + @inline(__always)
-
原则:
@inlinable→ 让调用方模块可见,增加跨模块特化机会@inline(__always)→ 强制内联小函数,减少调用开销
-
实践:
@inlinable @inline(__always)
func square<T: Numeric>(_ x: T) -> T { x * x }
-
注意:
- 大函数滥用 → 二进制膨胀
- 保留热点小函数使用 → 性能收益明显
6️⃣ 总结策略表
| 维度 | 具体做法 | 目标 |
|---|---|---|
| 函数设计 | 小函数、拆分、hot path 小 | 易内联 |
| 泛型设计 | 明确约束、避免深层嵌套 | 增加特化可能 |
| 模块设计 | 热点同模块、@inlinable | 跨模块可特化 |
| 调用模式 | inout、局部副本、批量操作 | 减少 CoW / 栈拷贝 |
| 内联提示 | @inlinable + @inline(__always) | 强制内联、消除函数调用开销 |
💡 核心理念:
让编译器看到具体类型 + 小函数 + 热路径 + 可见实现 → 特化和内联机会最大化,同时保持值语义安全。