golang defer性能損耗和實際使用場景

golang defer性能損耗和實際使用場景

我們常常聽到別人說:”defer 在棧退出時執行,會有性能損耗,儘量不要用。“ 前面的博客 defer原理 我們分析了defer延遲調用的底層實現原理 。下面我們就基於那篇原理分析文章,來分析一下 defer 延遲調用的性能損耗。

基準測試

package main

import (
	"sync"
	"testing"
)

var lock sync.Mutex

func NoDefer() {
	lock.Lock()
	lock.Unlock()
}
func Defer() {
	lock.Lock()
	defer lock.Unlock()
}

func BenchmarkNoDefer(b *testing.B) {
	for i := 0; i < b.N; i++ {
		NoDefer()
	}
}

func BenchmarkDefer(b *testing.B) {
	for i := 0; i < b.N; i++ {
		Defer()
	}
}

benchmark的執行結果如下:

ytlou@ytlou-mac  ~/Desktop/golang/golang_study/study/basic/defer   master ●✚  go test -bench=. defer_latency_test.go
goos: darwin
goarch: amd64
BenchmarkNoDefer-12     90020019                11.3 ns/op
BenchmarkDefer-12       37018846                33.0 ns/op
PASS
ok      command-line-arguments  2.294s

我們19款16G內存Mac上測試,結果顯示使用defer後的函數開銷確實比沒使用高了不少,這損耗用到哪裏去了呢?

defer開銷

我們回憶一下 defer原理 中對於defer延遲調用編譯成彙編之後的彙編代碼,defer 關鍵字其實涉及了一系列的連鎖調用,內部 runtime 函數的調用就至少多了三步,分別是 runtime.deferproc 一次和 runtime.deferreturn 兩次。

而這還只是在運行時的顯式動作,另外編譯器做的事也不少,例如:

  • 在 deferprocStack或者deferproc 階段(註冊延遲調用),還得獲取/傳入目標函數地址、函數參數等等。
  • 在 deferreturn 階段,需要在函數調用結尾處插入該方法的調用,同時若有被 defer 的函數,還需要使用 runtime·jmpdefer 進行跳轉以便於後續調用。

這一些動作途中還要涉及最小單元 _defer 的獲取/創建(在堆或棧), defer 和 recover 鏈表的邏輯處理和消耗等動作。

實際場景

defer 很多使用場景都是資源的close, 確保資源能夠在函數棧調用結束的時候釋放資源。比如下面的例子:

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)

嗯,一個請求當然沒問題,流量、併發一下子大了呢,那可能就是個災難了。你想想爲什麼?併發高的時候,持有成千上萬的 http response對象,但是又沒有及時的釋放掉,而是使用 defer延遲釋放,導致併發高的時候造成很嚴重的性能問題。

從常見的 defer + close 的使用組合來講,用之前建議先看清楚應用場景,在保證無異常的情況下確保儘早關閉纔是首選。如果只是小範圍調用很快就返回的話,偷個懶直接一套組合拳出去也未嘗不可。

結論

一個 defer 關鍵字實際上包含了不少的動作和處理,和你單純調用一個函數一條指令是沒法比的。而與對照物相比,它確確實實是有性能損耗,目前延遲調用的全部開銷大約在 30ns,但 defer 所提供的作用遠遠大於此,你從全局來看,它的損耗非常小,並且官方還不斷地在優化中。

因此,對於 “Go defer 會有性能損耗,儘量不能用?” 這個問題,我認爲該用就用,應該及時關閉就不要延遲,使用時一定要想清楚場景。

還有一點:defer使用最重要的不是性能問題,defer 最大的功能是 Panic 後依然有效。如果沒有 defer,Panic 後就會導致 unlock 丟失,從而導致死鎖了。 我覺得這個case非常經典。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章