摘要:原文地址會(huì)有性能損耗,盡量不要用上個(gè)月在軒脈刃的全棧技術(shù)群里看到一個(gè)小伙伴問說在棧退出時(shí)執(zhí)行,會(huì)有性能損耗,盡量不要用,這個(gè)怎么解。因此,對于會(huì)有性能損耗,盡量不能用這個(gè)問題,我認(rèn)為該用就用,應(yīng)該及時(shí)關(guān)閉就不要延遲,在用時(shí)一定要想清楚場景。
原文地址:Go defer 會(huì)有性能損耗,盡量不要用?
上個(gè)月在 @polaris @軒脈刃 的全棧技術(shù)群里看到一個(gè)小伙伴問 “說 defer 在棧退出時(shí)執(zhí)行,會(huì)有性能損耗,盡量不要用,這個(gè)怎么解?”。
恰好前段時(shí)間寫了一篇 《深入理解 Go defer》 去詳細(xì)剖析 defer 關(guān)鍵字。那么這一次簡單結(jié)合前文對這個(gè)問題進(jìn)行探討一波,希望對你有所幫助,但在此之前希望你花幾分鐘,自己思考一下答案,再繼續(xù)往下看。
測試func DoDefer(key, value string) { defer func(key, value string) { _ = key + value }(key, value) } func DoNotDefer(key, value string) { _ = key + value }
基準(zhǔn)測試:
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") } }
輸出結(jié)果:
$ 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
從結(jié)果上來,使用 defer 后的函數(shù)開銷確實(shí)比沒使用高了不少,這損耗用到哪里去了呢?
想一下$ 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 關(guān)鍵字其實(shí)涉及了一系列的連鎖調(diào)用,內(nèi)部 runtime 函數(shù)的調(diào)用就至少多了三步,分別是 runtime.deferproc 一次和 runtime.deferreturn 兩次。
而這還只是在運(yùn)行時(shí)的顯式動(dòng)作,另外編譯器做的事也不少,例如:
在 deferproc 階段(注冊延遲調(diào)用),還得獲取/傳入目標(biāo)函數(shù)地址、函數(shù)參數(shù)等等。
在 deferreturn 階段,需要在函數(shù)調(diào)用結(jié)尾處插入該方法的調(diào)用,同時(shí)若有被 defer 的函數(shù),還需要使用 runtime·jmpdefer 進(jìn)行跳轉(zhuǎn)以便于后續(xù)調(diào)用。
這一些動(dòng)作途中還要涉及最小單元 _defer 的獲取/生成, defer 和 recover 鏈表的邏輯處理和消耗等動(dòng)作。
Q&A最后討論的時(shí)候有提到 “問題指的是本來就是用來執(zhí)行 close() 一些操作的,然后說盡量不能用,例子就把 defer db.close() 前面的 defer 刪去了” 這個(gè)疑問。
這是一個(gè)比較類似 “教科書” 式的說法,在一些入門教程中會(huì)潛移默化的告訴你在資源控制后加個(gè) defer 延遲關(guān)閉一下。例如:
resp, err := http.Get(...) if err != nil { return err } defer resp.Body.Close()
但是一定得這么寫嗎?其實(shí)并不,很多人給出的理由都是 “怕你忘記” 這種說辭,這沒有毛病。但需要認(rèn)清場景,假設(shè)我的應(yīng)用場景如下:
resp, err := http.Get(...) if err != nil { return err } defer resp.Body.Close() // do something time.Sleep(time.Second * 60)
嗯,一個(gè)請求當(dāng)然沒問題,流量、并發(fā)一下子大了呢,那可能就是個(gè)災(zāi)難了。你想想為什么?從常見的 defer + close 的使用組合來講,用之前建議先看清楚應(yīng)用場景,在保證無異常的情況下確保盡早關(guān)閉才是首選。如果只是小范圍調(diào)用很快就返回的話,偷個(gè)懶直接一套組合拳出去也未嘗不可。
結(jié)論一個(gè) defer 關(guān)鍵字實(shí)際上包含了不少的動(dòng)作和處理,和你單純調(diào)用一個(gè)函數(shù)一條指令是沒法比的。而與對照物相比,它確確實(shí)實(shí)是有性能損耗,目前延遲調(diào)用的全部開銷大約在 50ns,但 defer 所提供的作用遠(yuǎn)遠(yuǎn)大于此,你從全局來看,它的損耗非常小,并且官方還不斷地在優(yōu)化中。
因此,對于 “Go defer 會(huì)有性能損耗,盡量不能用?” 這個(gè)問題,我認(rèn)為該用就用,應(yīng)該及時(shí)關(guān)閉就不要延遲,在 hot paths 用時(shí)一定要想清楚場景。
補(bǔ)充補(bǔ)充上柴大的回復(fù):“不是性能問題,defer 最大的功能是 Panic 后依然有效。如果沒有 defer,Panic 后就會(huì)導(dǎo)致 unlock 丟失,從而導(dǎo)致死鎖了”,非常經(jīng)典。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/31733.html
摘要:回調(diào)函數(shù)將固定為異步執(zhí)行。這些將被移除這些應(yīng)該會(huì)保留需要注意的是,那些繼續(xù)存在的回調(diào)函數(shù)不會(huì)有任何變化,只有的方法會(huì)受影響。 創(chuàng)作不夠,譯文來湊。 跟上篇一樣是編譯,不準(zhǔn)備逐字翻。比如,我會(huì)把we譯成jQuery官方團(tuán)隊(duì),或者他們。 初譯版,待校正。這篇文章比較長,翻譯難度也不小,如果有問題,歡迎提出,我盡量修改。 正文開始。 歪果仁也要雙喜臨門,于是 jQuery 官方團(tuán)隊(duì)選在 j...
摘要:后端好書閱讀與推薦系列文章后端好書閱讀與推薦后端好書閱讀與推薦續(xù)后端好書閱讀與推薦續(xù)二后端好書閱讀與推薦續(xù)三后端好書閱讀與推薦續(xù)四這里依然記錄一下每本書的亮點(diǎn)與自己讀書心得和體會(huì),分享并求拍磚。 后端好書閱讀與推薦系列文章:后端好書閱讀與推薦后端好書閱讀與推薦(續(xù))后端好書閱讀與推薦(續(xù)二)后端好書閱讀與推薦(續(xù)三)后端好書閱讀與推薦(續(xù)四) 這里依然記錄一下每本書的亮點(diǎn)與自己讀書心得...
摘要:界面上的更改都是通過操作實(shí)現(xiàn)的,并不是通過傳統(tǒng)的刷新頁面實(shí)現(xiàn)的。操作優(yōu)化的總原則是盡量減少操作。通過在文檔片段上進(jìn)行操作,可以降低操作對頁面性能的影響,這種方式是創(chuàng)建一個(gè)文檔片段,并在此片段上進(jìn)行必要的操作,操作完成后將它附加在頁面中。 界面上UI的更改都是通過DOM操作實(shí)現(xiàn)的,并不是通過傳統(tǒng)的刷新頁面實(shí)現(xiàn) 的。盡管DOM提供了豐富接口供外部調(diào)用,但DOM操作的代價(jià)很高,頁面前端代碼的...
摘要:加載的模塊會(huì)以參數(shù)形式傳入該函數(shù),從而在回調(diào)函數(shù)內(nèi)部就可以使用這些模塊。異步加載,和,瀏覽器不會(huì)失去響應(yīng)它指定的回調(diào)函數(shù),只有前面的模塊都加載成功后,才會(huì)運(yùn)行,解決了依賴性的問題。插件,可以讓回調(diào)函數(shù)在頁面結(jié)構(gòu)加載完成后再運(yùn)行。 這次主要是對《高性能JavaScript》一書的讀書筆記,記錄下自己之前沒有注意到或者需要引起重視的地方 第一章 加載和執(zhí)行 js代碼在執(zhí)行過程中會(huì)阻塞瀏覽...
摘要:所謂高并發(fā),就是同一時(shí)間有很多流量通常指用戶訪問程序的接口頁面及其他資源,解決高并發(fā)就是當(dāng)流量峰值到來時(shí)保證程序的穩(wěn)定性。索引多主多從分布式數(shù)據(jù)庫緩存連接池消息隊(duì)列等是從數(shù)據(jù)庫方便考慮如何優(yōu)化性能。 所謂高并發(fā),就是同一時(shí)間有很多流量(通常指用戶)訪問程序的接口、頁面及其他資源,解決高并發(fā)就是當(dāng)流量峰值到來時(shí)保證程序的穩(wěn)定性。 我們一般用QPS(每秒查詢數(shù),又叫每秒請求數(shù))來衡量程序的...
閱讀 935·2023-04-25 23:40
閱讀 3714·2021-11-22 15:22
閱讀 3555·2021-10-09 09:44
閱讀 3408·2021-09-23 11:52
閱讀 1266·2021-09-22 15:43
閱讀 793·2021-09-10 10:51
閱讀 2212·2021-09-06 15:02
閱讀 3207·2021-09-06 15:02