直接回答:Go 1.17 之前函数调用约定是纯栈式的:参数与返回值全部通过栈传递(这样 goroutine 栈可增长、实现简单)。internal ABI 改为像 SysV 一样优先用寄存器传参(amd64 上约 9 个整数寄存器加若干浮点寄存器),溢出才走栈。官方 benchmark 平均约 5% 的性能提升,调用密集的程序收益更明显。
展开解析:为什么以前不用:栈式 ABI 对可增长的分段/连续栈、栈扫描(GC 要精确扫描栈上的指针)实现最简单;寄存器 ABI 要求编译器在溢出点(spill)维护精确的指针位图,复杂度大增,这也是为什么 Go 团队拖了很多年。影响面:汇编代码——手写汇编要适配新约定(内部汇编有兼容封装,但与 C 互操作、反射调用、一些底层 trick 受影响);调试器与性能分析器需要理解新的栈布局; reflect 大参数调用的开销模型变了。Go 维持了两套 ABI:internal ABI 用于 Go 调 Go,ABI0 保留给旧汇编,链接器插入 wrapper 转换,这是平滑迁移的关键设计。对开发者的实践意义很小(无需改代码),但面试中它体现 Go 演进的典型路径:先保证正确性与可移植性(栈式 ABI、保守实现),性能债留到生态成熟后用兼容机制偿还——与分段栈改连续栈、抢占式调度的演进一脉相承。
追问方向:寄存器 ABI 下 GC 如何找到寄存器里的指针?对 cgo 调用有什么影响?
(约 480 字)