中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Go語言defer語句的三種機制整理

發布時間:2020-09-05 01:00:52 來源:腳本之家 閱讀:175 作者:面向人生編程 欄目:編程語言

Golang 的 1.13 版本 與 1.14 版本對 defer 進行了兩次優化,使得 defer 的性能開銷在大部分場景下都得到大幅降低,其中到底經歷了什么原理?

這是因為這兩個版本對 defer 各加入了一項新的機制,使得 defer 語句在編譯時,編譯器會根據不同版本與情況,對每個 defer 選擇不同的機制,以更輕量的方式運行調用。

堆上分配

在 Golang 1.13 之前的版本中,所有 defer 都是在堆上分配,該機制在編譯時會進行兩個步驟:

  1. 在 defer 語句的位置插入 runtime.deferproc,當被執行時,延遲調用會被保存為一個 _defer 記錄,并將被延遲調用的入口地址及其參數復制保存,存入 Goroutine 的調用鏈表中。
  2. 在函數返回之前的位置插入 runtime.deferreturn,當被執行時,會將延遲調用從 Goroutine 鏈表中取出并執行,多個延遲調用則以 jmpdefer 尾遞歸調用方式連續執行。

這種機制的主要性能問題存在于每個 defer 語句產生記錄時的內存分配,以及記錄參數和完成調用時參數移動的系統調用開銷。

棧上分配

Go 1.13 版本新加入 deferprocStack 實現了在棧上分配的形式來取代 deferproc,相比后者,棧上分配在函數返回后 _defer 便得到釋放,省去了內存分配時產生的性能開銷,只需適當維護 _defer 的鏈表即可。

編譯器有自己的邏輯去選擇使用 deferproc 還是 deferprocStack,大部分情況下都會使用后者,性能會提升約 30%。不過在 defer 語句出現在了循環語句里,或者無法執行更高階的編譯器優化時,亦或者同一個函數中使用了過多的 defer 時,依然會使用 deferproc。

開放編碼

Go 1.14 版本繼續加入了開發編碼(open coded),該機制會將延遲調用直接插入函數返回之前,省去了運行時的 deferproc 或 deferprocStack 操作,在運行時的 deferreturn 也不會進行尾遞歸調用,而是直接在一個循環中遍歷所有延遲函數執行。

這種機制使得 defer 的開銷幾乎可以忽略,唯一的運行時成本就是存儲參與延遲調用的相關信息,不過使用此機制需要一些條件:

  1. 沒有禁用編譯器優化,即沒有設置 -gcflags "-N";
  2. 函數內 defer 的數量不超過 8 個,且返回語句與延遲語句個數的乘積不超過 15;
  3. defer 不是在循環語句中。

該機制還引入了一種元素 —— 延遲比特(defer bit),用于運行時記錄每個 defer 是否被執行(尤其是在條件判斷分支中的 defer),從而便于判斷最后的延遲調用該執行哪些函數。

延遲比特的原理:

同一個函數內每出現一個 defer 都會為其分配 1 個比特,如果被執行到則設為 1,否則設為 0,當到達函數返回之前需要判斷延遲調用時,則用掩碼判斷每個位置的比特,若為 1 則調用延遲函數,否則跳過。

為了輕量,官方將延遲比特限制為 1 個字節,即 8 個比特,這就是為什么不能超過 8 個 defer 的原因,若超過依然會選擇堆棧分配,但顯然大部分情況不會超過 8 個。

用代碼演示如下:

deferBits = 0 // 延遲比特初始值 00000000

deferBits |= 1<<0 // 執行第一個 defer,設置為 00000001
_f1 = f1 // 延遲函數
_a1 = a1 // 延遲函數的參數
if cond {
  // 如果第二個 defer 被執行,則設置為 00000011,否則依然為 00000001
  deferBits |= 1<<1
  _f2 = f2
  _a2 = a2
}
...
exit:
// 函數返回之前,倒序檢查延遲比特,通過掩碼逐位進行與運算,來判斷是否調用函數

// 假如 deferBits 為 00000011,則 00000011 & 00000010 != 0,因此調用 f2
// 否則 00000001 & 00000010 == 0,不調用 f2
if deferBits & 1<<1 != 0 {
  deferBits &^= 1<<1 // 移位為下次判斷準備
  _f2(_a2)
}
// 同理,由于 00000001 & 00000001 != 0,調用 f1
if deferBits && 1<<0 != 0 {
  deferBits &^= 1<<0
  _f1(_a1)
}

總結

以往 Golang defer 語句的性能問題一直飽受詬病,最近正式發布的 1.14 版本終于為這個爭議畫上了階段性的句號。如果不是在特殊情況下,我們不需要再計較 defer 的性能開銷。

參考資料

[1] Ou Changkun - Go 語言原本

[2] 峰云就她了 - go1.14實現defer性能大幅度提升原理

[3] 34481-opencoded-defers

到此這篇關于Go語言defer語句的三種機制整理的文章就介紹到這了,更多相關探究Go語言defer語句的三種機制內容請搜索億速云以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持億速云!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

南城县| 闽侯县| 宣恩县| 准格尔旗| 莎车县| 洛南县| 普定县| 永兴县| 镇赉县| 河东区| 喜德县| 搜索| 德庆县| 镇沅| 讷河市| 兴仁县| 东乌珠穆沁旗| 阿城市| 临沂市| 大英县| 洛隆县| 临夏县| 台北县| 吴忠市| 巴青县| 内江市| 柘城县| 奉新县| 苏州市| 朝阳县| 香河县| 正宁县| 监利县| 夏津县| 关岭| 周至县| 溆浦县| 平顶山市| 衢州市| 马公市| 泗洪县|