资讯专栏INFORMATION COLUMN

Go defer 会有性能损耗,尽量不要用?

wangshijun / 767人阅读

摘要:原文地址会有性能损耗,尽量不要用上个月在轩脉刃的全栈技术群里看到一个小伙伴问说在栈退出时执行,会有性能损耗,尽量不要用,这个怎么解。因此,对于会有性能损耗,尽量不能用这个问题,我认为该用就用,应该及时关闭就不要延迟,在用时一定要想清楚场景。

原文地址:Go defer 会有性能损耗,尽量不要用?

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

恰好前段时间写了一篇 《深入理解 Go defer》 去详细剖析 defer 关键字。那么这一次简单结合前文对这个问题进行探讨一波,希望对你有所帮助,但在此之前希望你花几分钟,自己思考一下答案,再继续往下看。

测试
func DoDefer(key, value string) {
    defer func(key, value string) {
        _ = key + value
    }(key, value)
}

func DoNotDefer(key, value string) {
    _ = key + value
}

基准测试:

func BenchmarkDoDefer(b *testing.B) {
    for i := 0; i < b.N; i++ {
        DoDefer("煎鱼", "https://github.com/EDDYCJY/blog")
    }
}

func BenchmarkDoNotDefer(b *testing.B) {
    for i := 0; i < b.N; i++ {
        DoNotDefer("煎鱼", "https://github.com/EDDYCJY/blog")
    }
}

输出结果:

$ go test -bench=. -benchmem -run=none
goos: darwin
goarch: amd64
pkg: github.com/EDDYCJY/awesomeDefer
BenchmarkDoDefer-4          20000000            91.4 ns/op          48 B/op           1 allocs/op
BenchmarkDoNotDefer-4       30000000            41.6 ns/op          48 B/op           1 allocs/op
PASS
ok      github.com/EDDYCJY/awesomeDefer    3.234s

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

想一下
$ go tool compile -S main.go 
"".main STEXT size=163 args=0x0 locals=0x40
    ...
    0x0059 00089 (main.go:6)    MOVQ    AX, 16(SP)
    0x005e 00094 (main.go:6)    MOVQ    $1, 24(SP)
    0x0067 00103 (main.go:6)    MOVQ    $1, 32(SP)
    0x0070 00112 (main.go:6)    CALL    runtime.deferproc(SB)
    0x0075 00117 (main.go:6)    TESTL    AX, AX
    0x0077 00119 (main.go:6)    JNE    137
    0x0079 00121 (main.go:7)    XCHGL    AX, AX
    0x007a 00122 (main.go:7)    CALL    runtime.deferreturn(SB)
    0x007f 00127 (main.go:7)    MOVQ    56(SP), BP
    0x0084 00132 (main.go:7)    ADDQ    $64, SP
    0x0088 00136 (main.go:7)    RET
    0x0089 00137 (main.go:6)    XCHGL    AX, AX
    0x008a 00138 (main.go:6)    CALL    runtime.deferreturn(SB)
    0x008f 00143 (main.go:6)    MOVQ    56(SP), BP
    0x0094 00148 (main.go:6)    ADDQ    $64, SP
    0x0098 00152 (main.go:6)    RET
    ...

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

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

deferproc 阶段(注册延迟调用),还得获取/传入目标函数地址、函数参数等等。

deferreturn 阶段,需要在函数调用结尾处插入该方法的调用,同时若有被 defer 的函数,还需要使用 runtime·jmpdefer 进行跳转以便于后续调用。

这一些动作途中还要涉及最小单元 _defer 的获取/生成, deferrecover 链表的逻辑处理和消耗等动作。

Q&A

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

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

resp, err := http.Get(...)
if err != nil {
    return err
}
defer resp.Body.Close()

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

resp, err := http.Get(...)
if err != nil {
    return err
}
defer resp.Body.Close()
// do something
time.Sleep(time.Second * 60)

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

结论

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

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

补充

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/31733.html

相关文章

  • jQuery 3.0 beta 发布

    摘要:回调函数将固定为异步执行。这些将被移除这些应该会保留需要注意的是,那些继续存在的回调函数不会有任何变化,只有的方法会受影响。 创作不够,译文来凑。 跟上篇一样是编译,不准备逐字翻。比如,我会把we译成jQuery官方团队,或者他们。 初译版,待校正。这篇文章比较长,翻译难度也不小,如果有问题,欢迎提出,我尽量修改。 正文开始。 歪果仁也要双喜临门,于是 jQuery 官方团队选在 j...

    LeanCloud 评论0 收藏0
  • 后端好书阅读与推荐(续四)

    摘要:后端好书阅读与推荐系列文章后端好书阅读与推荐后端好书阅读与推荐续后端好书阅读与推荐续二后端好书阅读与推荐续三后端好书阅读与推荐续四这里依然记录一下每本书的亮点与自己读书心得和体会,分享并求拍砖。 后端好书阅读与推荐系列文章:后端好书阅读与推荐后端好书阅读与推荐(续)后端好书阅读与推荐(续二)后端好书阅读与推荐(续三)后端好书阅读与推荐(续四) 这里依然记录一下每本书的亮点与自己读书心得...

    phodal 评论0 收藏0
  • 前端页面卡顿、也许是DOM操作惹的祸?

    摘要:界面上的更改都是通过操作实现的,并不是通过传统的刷新页面实现的。操作优化的总原则是尽量减少操作。通过在文档片段上进行操作,可以降低操作对页面性能的影响,这种方式是创建一个文档片段,并在此片段上进行必要的操作,操作完成后将它附加在页面中。 界面上UI的更改都是通过DOM操作实现的,并不是通过传统的刷新页面实现 的。尽管DOM提供了丰富接口供外部调用,但DOM操作的代价很高,页面前端代码的...

    AnthonyHan 评论0 收藏0
  • 《高性能JavaScript》(读书笔记)

    摘要:加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块。异步加载,和,浏览器不会失去响应它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。插件,可以让回调函数在页面结构加载完成后再运行。 这次主要是对《高性能JavaScript》一书的读书笔记,记录下自己之前没有注意到或者需要引起重视的地方 第一章 加载和执行 js代码在执行过程中会阻塞浏览...

    moven_j 评论0 收藏0
  • 面试官问你如何解决web高并发这样回答就好了

    摘要:所谓高并发,就是同一时间有很多流量通常指用户访问程序的接口页面及其他资源,解决高并发就是当流量峰值到来时保证程序的稳定性。索引多主多从分布式数据库缓存连接池消息队列等是从数据库方便考虑如何优化性能。 所谓高并发,就是同一时间有很多流量(通常指用户)访问程序的接口、页面及其他资源,解决高并发就是当流量峰值到来时保证程序的稳定性。 我们一般用QPS(每秒查询数,又叫每秒请求数)来衡量程序的...

    Achilles 评论0 收藏0

发表评论

0条评论

wangshijun

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<