1️⃣ @inlinable
定义
- 告诉 编译器和模块外调用方:
这个函数的 函数体可以被内联 到调用方模块
- 常用于 库函数/框架函数,允许跨模块内联
特点
- 函数体 会暴露在模块接口中(即 public 或 internal 可见)
- 编译器可选择性内联(heuristic 内联)
- 支持 泛型特化优化:调用方已知类型 → 可生成特化版本
使用场景
- 公共库函数:Swift 标准库经常用
@inlinable
public func square<T: Numeric>(_ x: T) -> T {
return x * x
}
- 调用方可以看到实现 → 泛型特化、优化
- 跨模块泛型函数:允许编译器生成特化版本
风险/缺点
-
暴露实现细节:
- 任何改变函数体 → 需要重新编译调用模块
-
二进制膨胀:
- 调用方内联后 → 编译器生成新函数体
-
不保证总是内联:
- 编译器可根据 heuristics 决定是否内联
2️⃣ @inline(__always)
定义
- 强制编译器尽量 总是内联函数
- 忽略 heuristics,几乎在每个调用点展开函数体
使用场景
- 小型热路径函数
@inline(__always)
func add(_ a: Int, _ b: Int) -> Int { a + b }
- 避免频繁函数调用开销
- 性能敏感的循环内部
- 避免函数调用、减少栈操作
- 结合泛型 → 可消除 CoW 或动态分发
风险/缺点
-
二进制膨胀明显:
- 每次调用点都会展开函数体 → 大函数在循环中会快速膨胀
-
缓存/编译时间增加:
- 大函数被多次展开 → instruction cache 压力大
-
可读性/维护性下降:
- 调试时栈展开复杂
-
仍受某些条件限制:
- 如果函数太大,LLVM 可能忽略强制内联
3️⃣ 区别总结
| 特性 | @inlinable | @inline(__always) |
|---|---|---|
| 内联保证 | 编译器选择性 | 尽量强制内联 |
| 模块可见性 | 会暴露实现(跨模块可见) | 不影响可见性 |
| 使用场景 | 库函数,泛型特化 | 小函数,热路径性能优化 |
| 风险 | 二进制膨胀、接口暴露 | 二进制膨胀大,缓存压力,调试复杂 |
| 泛型优化 | 支持跨模块特化 | 支持,但风险更高 |
4️⃣ 实践建议
-
库开发者
- 公共泛型函数 → 用
@inlinable,允许特化优化 - 不要滥用
@inline(__always),只对极小函数、热点函数使用
- 公共泛型函数 → 用
-
应用开发者
- 性能热点小函数 → 可用
@inline(__always) - 普通函数 → 编译器 heuristics 足够
- 性能热点小函数 → 可用
-
组合使用
@inlinable @inline(__always)→ 表示跨模块可内联且强制内联- 风险高,只在非常性能敏感的函数才用
💡 总结原则:
@inlinable→ 跨模块可内联,泛型特化优化@inline(__always)→ 强制局部内联,热路径性能优化- 风险主要在 二进制膨胀和缓存压力,滥用可能得不偿失