直接回答:早期 defer 在运行时装配:每次执行 defer 语句都在堆上分配 defer 记录挂入链表,函数返回时逐条调用,单次开销约 50ns。Go 1.14 的 open-coded defer 把“可静态分析”的 defer(在循环外、数量不超过 8 个)直接展开成普通代码:编译器在函数帧里放一个位掩码记录哪些 defer 已激活,返回路径上按掩码内联调用目标函数,完全没有链表与堆分配,开销降到约 1ns 量级。
展开解析:这是“把运行时工作挪给编译器”的典型案例。条件限制要知道:循环内的 defer 无法静态确定数量,回退到老的链表方式(1.13 已把记录挪到栈上缓存了一部分);panic 路径仍要能正确执行已激活的 defer,open-coded 版本靠位掩码在展开时恢复执行序列。实践含义:普通函数里的一两个 defer(关文件、解锁、打日志)现在几乎免费,性能代码不必为了 defer 纠结;但 for 循环里 defer 关闭资源仍是双重错误——既退化到老路径又推迟资源释放,应该包一层函数。可观测证据:benchmark 1.13 与 1.14 的 defer 开销差一个数量级;逃逸分析里 defer 记录的堆分配消失。面试可延伸:Go 的优化哲学偏向“让常见写法变快”而不是要求用户写特殊代码,类似的还有 map 的 Swiss Table 化、interface 转换缓存。
追问方向:为什么循环内 defer 回退老路径?defer 与 return 命名返回值怎么交互?
(约 460 字)