Go 性能优化技巧 4/10

延迟调用(defer)确实是一种 “优雅” 机制。可简化代码,并确保即便发生 panic 依然会被执行。如将 panic/recover 比作 try/except,那么 defer 似乎可看做 finally。

如同异常保护被滥用一样,defer 被无限制使用的例子比比皆是。

Go 性能优化技巧 4/10
Go 性能优化技巧 4/10

只需稍稍了解 defer 实现机制,就不难理解会有这样的性能差异。

编译器通过 runtime.deferproc “注册” 延迟调用,除目标函数地址外,还会复制相关参数(包括 receiver)。在函数返回前,执行 runtime.deferreturn 提取相关信息执行延迟调用。这其中的代价自然不是普通函数调用一条 CALL 指令所能比拟的。

Go 性能优化技巧 4/10
Go 性能优化技巧 4/10

或许你会觉得 4x 的性能差异算不得什么,但如果是下面这样呢?

Go 性能优化技巧 4/10

当多个 goroutine 执行该函数时,只怕性能差异就不是 4x,还得算上 httpGet 所需时间。原本的并发设计,因为错误的 defer 调用变成 “串行”。

与之类似的,还有下面这样的写法。

Go 性能优化技巧 4/10

如果 files 是个 “超大” 列表,只怕在 analysis 结束前,会有不小的隐式 “资源泄露”,这些不能及时回收的对象,会导致 GC 在内的相关性能问题。

解决方法么,要么去掉 f.close 前的 defer,要么将内层处理逻辑重构为独立函数(比如匿名函数调用)。

Go 性能优化技巧 4/10

除此之外,单个函数里过多的 defer 调用可尝试合并。最起码,在并发竞争激烈时,mutex.Unlock 不应该使用 defer,而应尽快执行,仅保护最短的代码片段。

最新动态,请扫码关注
Go 性能优化技巧 4/10