分享
  1. 首页
  2. 文章

使用 defer 的运行时开销

saberuster · · 2024 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

在 Go 语言中有一个特殊的关键字 `defer`。对于它更多的介绍请看[这里](https://blog.golang.org/defer-panic-and-recover)。`defer` 语句会把一个函数追加到函数调用列表。这个列表会在函数返回的时候依次调用。`defer` 常用来进行各种清理操作。 但是 `defer` 本身是有开销的。使用 Go 的基准测试工具我们可以量化这种开销。 下面两个函数做同样的工作。一个使用 `defer` 语句而另一个不使用: ```go package main func doNoDefer(t *int) { func() { *t++ }() } func doDefer(t *int) { defer func() { *t++ }() } ``` 基准测试代码: ```go package main import ( "testing" ) func BenchmarkDeferYes(b *testing.B) { t := 0 for i := 0; i < b.N; i++ { doDefer(&t) } } func BenchmarkDeferNo(b *testing.B) { t := 0 for i := 0; i < b.N; i++ { doNoDefer(&t) } } ``` 在一个 8 核的谷歌云主机上运行基准测试: ``` ⇒ go test -v -bench BenchmarkDefer -benchmem goos: linux goarch: amd64 pkg: cmd BenchmarkDeferYes-8 20000000 62.4 ns/op 0 B/op 0 allocs/op BenchmarkDeferNo-8 500000000 3.70 ns/op 0 B/op 0 allocs/op ``` 和预想的一样,这些函数都没有额外分配任何内存。但是 `doDefer` 的开销要比 `doNoDefer` 高 16 倍之多。我们需要借助反汇编代码来了解为什么 `defer` 的开销如此之大。 反汇编代码的函数调用部分 `doDefer` 和 `doNoDefer` 是相同的。 ``` main.go:10 MOVQ 0x8(SP), AX main.go:11 MOVQ 0(AX), CX main.go:11 INCQ CX main.go:11 MOVQ CX, 0(AX) main.go:12 RET ``` `doNoDefer` 先初始化必要的注册工作然后调用 `main.doNoDefer.func1`。 ``` TEXT main.doNoDefer(SB) main.go main.go:3 MOVQ FS:0xfffffff8, CX main.go:3 CMPQ 0x10(CX), SP main.go:3 JBE 0x450b65 main.go:3 SUBQ 0ドルx10, SP main.go:3 MOVQ BP, 0x8(SP) main.go:3 LEAQ 0x8(SP), BP main.go:3 MOVQ 0x18(SP), AX main.go:6 MOVQ AX, 0(SP) main.go:6 CALL main.doNoDefer.func1(SB) main.go:7 MOVQ 0x8(SP), BP main.go:7 ADDQ 0ドルx10, SP main.go:7 RET main.go:3 CALL runtime.morestack_noctxt(SB) main.go:3 JMP main.doNoDefer(SB) ``` `doDefer` 也会先进行必要的注册工作,但是它会额外调用几个函数:第一个是 `runtime.deferproc`,它用来设置需要调用的延迟函数。第二个是 `runtime.deferreturn`,它会自动调用每个 `defer` 语句。 ``` TEXT main.doDefer(SB) main.go main.go:9 MOVQ FS:0xfffffff8, CX main.go:9 CMPQ 0x10(CX), SP main.go:9 JBE 0x450bd3 main.go:9 SUBQ 0ドルx20, SP main.go:9 MOVQ BP, 0x18(SP) main.go:9 LEAQ 0x18(SP), BP main.go:9 MOVQ 0x28(SP), AX main.go:12 MOVQ AX, 0x10(SP) main.go:10 MOVL 0ドルx8, 0(SP) main.go:10 LEAQ 0x218e3(IP), AX main.go:10 MOVQ AX, 0x8(SP) main.go:10 CALL runtime.deferproc(SB) main.go:10 TESTL AX, AX main.go:10 JNE 0x450bc3 main.go:13 NOPL main.go:13 CALL runtime.deferreturn(SB) main.go:13 MOVQ 0x18(SP), BP main.go:13 ADDQ 0ドルx20, SP main.go:13 RET main.go:10 NOPL main.go:10 CALL runtime.deferreturn(SB) main.go:10 MOVQ 0x18(SP), BP main.go:10 ADDQ 0ドルx20, SP main.go:10 RET main.go:9 CALL runtime.morestack_noctxt(SB) main.go:9 JMP main.doDefer(SB) ``` `deferproc` 和 `deferreturn` 都是比较复杂的函数,它们会在进入和退出函数时进行一系列的配置和计算。所以,不要在热代码中使用 `defer` 关键字,因为它的开销很大的而且很难被侦测到。

via: https://medium.com/i0exception/runtime-overhead-of-using-defer-in-go-7140d5c40e32

作者:Aniruddha 译者:saberuster 校对:polaris1119

本文由 GCTT 原创编译,Go语言中文网 荣誉推出

本文由 GCTT 原创翻译,Go语言中文网 首发。也想加入译者行列,为开源做一些自己的贡献么?欢迎加入 GCTT!
翻译工作和译文发表仅用于学习和交流目的,翻译工作遵照 CC-BY-NC-SA 协议规定,如果我们的工作有侵犯到您的权益,请及时联系我们。

欢迎遵照 CC-BY-NC-SA 协议规定 转载,敬请在正文中标注并保留原文/译文链接和作者/译者等信息。
文章仅代表作者的知识和看法,如有不同观点,请楼下排队吐槽


有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
2024 次点击
被以下专栏收入,发现更多相似内容
2 回复 | 直到 2018年06月09日 12:09:50
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏