這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。在 Go 語言中有一個特殊的關鍵字 `defer`。對於它更多的介紹請看[這裡](https://blog.golang.org/defer-panic-and-recover)。`defer` 語句會把一個函數追加到函數調用列表。這個列表會在函數返回的時候依次調用。`defer` 常用來進行各種清理操作。但是 `defer` 本身是有開銷的。使用 Go 的基準測試載入器我們可以量化這種開銷。下面兩個函數做同樣的工作。一個使用 `defer` 語句而另一個不使用:```gopackage mainfunc doNoDefer(t *int) {func() {*t++}()}func doDefer(t *int) {defer func() {*t++}()}```基準測試代碼:```gopackage mainimport ("testing")func BenchmarkDeferYes(b *testing.B) {t := 0for i := 0; i < b.N; i++ {doDefer(&t)}}func BenchmarkDeferNo(b *testing.B) {t := 0for i := 0; i < b.N; i++ {doNoDefer(&t)}}```在一個 8 核的Google雲主機上運行基準測試:```⇒ go test -v -bench BenchmarkDefer -benchmemgoos: linuxgoarch: amd64pkg: cmdBenchmarkDeferYes-8 20000000 62.4 ns/op 0 B/op 0 allocs/opBenchmarkDeferNo-8 500000000 3.70 ns/op 0 B/op 0 allocs/op```和預想的一樣,這些函數都沒有額外分配任何記憶體。但是 `doDefer` 的開銷要比 `doNoDefer` 高 16 倍之多。我們需要藉助反組譯碼代碼來瞭解為什麼 `defer` 的開銷如此之大。反組譯碼代碼的函數調用部分 `doDefer` 和 `doNoDefer` 是相同的。```main.go:10 MOVQ 0x8(SP), AXmain.go:11 MOVQ 0(AX), CXmain.go:11 INCQ CXmain.go:11 MOVQ CX, 0(AX)main.go:12 RET````doNoDefer` 先初始化必要的註冊工作然後調用 `main.doNoDefer.func1`。```TEXT main.doNoDefer(SB) main.gomain.go:3 MOVQ FS:0xfffffff8, CXmain.go:3 CMPQ 0x10(CX), SPmain.go:3 JBE 0x450b65main.go:3 SUBQ $0x10, SPmain.go:3 MOVQ BP, 0x8(SP)main.go:3 LEAQ 0x8(SP), BPmain.go:3 MOVQ 0x18(SP), AXmain.go:6 MOVQ AX, 0(SP)main.go:6 CALL main.doNoDefer.func1(SB)main.go:7 MOVQ 0x8(SP), BPmain.go:7 ADDQ $0x10, SPmain.go:7 RETmain.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.gomain.go:9 MOVQ FS:0xfffffff8, CXmain.go:9 CMPQ 0x10(CX), SPmain.go:9 JBE 0x450bd3main.go:9 SUBQ $0x20, SPmain.go:9 MOVQ BP, 0x18(SP)main.go:9 LEAQ 0x18(SP), BPmain.go:9 MOVQ 0x28(SP), AXmain.go:12 MOVQ AX, 0x10(SP)main.go:10 MOVL $0x8, 0(SP)main.go:10 LEAQ 0x218e3(IP), AXmain.go:10 MOVQ AX, 0x8(SP)main.go:10 CALL runtime.deferproc(SB)main.go:10 TESTL AX, AXmain.go:10 JNE 0x450bc3main.go:13 NOPLmain.go:13 CALL runtime.deferreturn(SB)main.go:13 MOVQ 0x18(SP), BPmain.go:13 ADDQ $0x20, SPmain.go:13 RETmain.go:10 NOPLmain.go:10 CALL runtime.deferreturn(SB)main.go:10 MOVQ 0x18(SP), BPmain.go:10 ADDQ $0x20, SPmain.go:10 RETmain.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 協議規定 轉載,敬請在本文中標註並保留原文/譯文連結和作者/譯者等資訊。
文章僅代表作者的知識和看法,如有不同觀點,請樓下排隊吐槽
422 次點擊