7-14.【高级特性】为什么返回 Never 可以帮助编译器进行优化?

1 阅读3分钟

返回 Never 并不只是给开发者看的“语义标记”,它对编译器(Swift Compiler)来说是一个非常强烈的信号。

在 Swift 的编译流程中,编译器会将代码转换为 SIL (Swift Intermediate Language) ,并在此阶段构建 控制流图 (Control Flow Graph, CFG)Never 的存在允许编译器对这个图进行深度剪枝。

以下是 Never 帮助优化的具体维度:


1. 控制流图 (CFG) 的剪枝

编译器在分析程序逻辑时,会将代码视为一系列相连的节点。普通的函数节点会有“进入”和“退出”两个连线。

  • 普通函数:编译器必须假设函数执行完后会回到调用处,因此必须保留调用点之后的节点。
  • Never 函数:编译器知道该节点是终点(Sink Node) 。它会直接切断该节点之后的所有连线。

优化结果: 编译器可以安全地删除所有“不可达代码(Unreachable Code)”。这不仅减少了生成的机器码体积(Binary Size),还让编译器能更专注于有效路径的优化。


2. 寄存器与栈帧管理的简化

在标准的函数调用约定(Calling Convention)中,调用一个函数前,编译器需要:

  1. 保存当前的寄存器状态。
  2. 在栈上分配空间。
  3. 准备好接收返回值的寄存器。

如果编译器确定一个函数返回 Never,它在调用处可以变得非常“粗暴”:

  • 无需清理栈:既然程序都要崩了或终止了,调用后的栈平衡(Stack Balancing)操作就没意义了。
  • 无需恢复寄存器:调用前保存的那些寄存器永远不会被重新加载。
  • 尾调用优化 (TCO) :编译器更容易将此类调用优化为直接跳转(Jump),而不需要维护复杂的调用链。

3. 提升分支预测器的效率

现代 CPU 使用分支预测(Branch Prediction)来提前加载指令。

当编译器知道某个分支(如 guardelse 块)最终会走向 Never 时,它会给指令流打上特殊的标记(如在汇编层面使用 unlikely 指令提示)。

  • 这能引导 CPU 优先预测“正常路径”,减少由于分支预测失败导致的流水线清空(Pipeline Flush)。
  • 性能提升:虽然单次忽略不计,但在高性能循环或底层框架中,这种确定性非常宝贵。

4. 完备性检查 (Exhaustiveness Check)

这是开发者感知最明显的一点。在处理 switch 语句时,如果某个 case 导致了 Never,编译器可以利用这一点来证明整个 switch 已经“穷尽”了所有可能。

Swift

enum Status { case ok, failed }

func handle(_ status: Status) -> Int {
    switch status {
    case .ok:
        return 200
    case .failed:
        fatalError() // 返回 Never
        // 这里不需要写 return,编译器知道这路不通,逻辑已经闭环
    }
}

如果这里返回的是 Void 而不是 Never,编译器会报错,强制要求你在 failed 分支也返回一个 Int。通过 Never,编译器减少了为了满足语法正确性而产生的冗余跳转指令。


总结:Never 的三层收益

维度收益
编译期加快编译速度,因为需要分析的代码路径减少了。
二进制体积自动剔除永远不会执行的指令块。
运行期更好的缓存利用率和更精准的 CPU 分支预测。