APP下载

有人说 Go defer 会有效能损耗 尽量不要用?

消息来源:baojiabao.com 作者: 发布时间:2024-05-09

报价宝综合消息有人说 Go defer 会有效能损耗 尽量不要用?

上个月在 @polaris @轩脉刃 的全栈技术群里看到一个小伙伴问 “说 defer 在栈退出时执行,会有效能损耗,尽量不要用,这个怎么解?”。

恰好前段时间写了一篇 golang 中 defer 的后进先出特性是怎么做到的呢? 去详细剖析 defer 关键字。那么这一次简单结合前文对这个问题进行探讨一波,希望对你有所帮助,但在此之前希望你花几分钟,自己思考一下答案,再继续往下看。

01 测试

基准测试:

输出结果:

从结果上来,使用 defer 后的函式开销确实比没使用高了不少,这损耗用到哪里去了呢?

02 想一下

我们在前文提到 defer 关键字其实涉及了一系列的连锁呼叫,内部 runtime 函式的呼叫就至少多了三步,分别是 runtime.deferproc 一次和 runtime.deferreturn 两次。

而这还只是在执行时的显式动作,另外编译器做的事也不少,例如:

在 deferproc 阶段(注册延迟呼叫),还得获取/传入目标函式地址、函式引数等等。在 deferreturn 阶段,需要在函式呼叫结尾处插入该方法的呼叫,同时若有被 defer 的函式,还需要使用 runtime·jmpdefer 进行跳转以便于后续呼叫。这一些动作途中还要涉及最小单元 _defer 的获取/生成, defer 和 recover 连结串列的逻辑处理和消耗等动作。

03 Q&A

最后讨论的时候有提到 “问题指的是本来就是用来执行 close() 一些操作的,然后说尽量不能用,例子就把 defer db.close() 前面的 defer 删去了” 这个疑问。

这是一个比较类似 “教科书” 式的说法,在一些入门教程中会潜移默化的告诉你在资源控制后加个 defer 延迟关闭一下。例如:

resp, err := http.Get(...)

if err != nil {

return err

}

defer resp.Body.Close()

但是一定得这么写吗?其实并不,很多人给出的理由都是 “怕你忘记” 这种说辞,这没有毛病。但需要认清场景,假设我的应用场景如下:

嗯,一个请求当然没问题,流量、并发一下子大了呢,那可能就是个灾难了。你想想为什么?从常见的 defer + close 的使用组合来讲,用之前建议先看清楚应用场景,在保证无异常的情况下确保尽早关闭才是首选。如果只是小范围呼叫很快就返回的话,偷个懒直接一套组合拳出去也未尝不可。

04 结论

一个 defer 关键字实际上包含了不少的动作和处理,和你单纯呼叫一个函式一条指令是没法比的。而与对照物相比,它确确实实是有效能损耗,目前延迟呼叫的全部开销大约在 50ns,但 defer 所提供的作用远远大于此,你从全域性来看,它的损耗非常小,并且官方还不断地在优化中。

因此,对于 “Go defer 会有效能损耗,尽量不能用?” 这个问题,我认为该用就用,应该及时关闭就不要延迟,在 hot paths 用时一定要想清楚场景。

05 补充

最后补充上柴大的回复:“不是效能问题,defer 最大的功能是 Panic 后依然有效。如果没有 defer,Panic 后就会导致 unlock 丢失,从而导致死锁了”,非常经典。

本文作者:煎鱼,原创授权释出

2019-12-13 07:52:00

相关文章