當前位置:編程學習大全網 - 編程語言 - Go語言使用 map 時盡量不要在 big map 中保存指針

Go語言使用 map 時盡量不要在 big map 中保存指針

不知道妳有沒有聽過這麽壹句:在使用 map 時盡量不要在 big map 中保存指針。好吧,妳現在已經聽過了:)為什麽呢?原因在於 Go 語言的垃圾回收器會掃描標記 map 中的所有元素,GC 開銷相當大,直接GG。

這兩天在《Mastering Go》中看到 GC 這壹章節裏面對比 map 和 slice 在垃圾回收中的效率對比,書中只給出結論沒有說明理由,這我是不能忍的,於是有了這篇學習筆記。扯那麽多,Show Your Code

這是壹個簡單的測試程序,保存字符串的 map 和 保存整形的 map GC 的效率相差幾十倍,是不是有同學會說明明保存的是 string 哪有指針?這個要說到 Go 語言中 string 的底層實現了,源碼在 src/runtime/string.go裏,可以看到 string 其實包含壹個指向數據的指針和壹個長度字段。註意這裏的是否包含指針,包括底層的實現。

Go 語言的 GC 會遞歸遍歷並標記所有可觸達的對象,標記完成之後將所有沒有引用的對象進行清理。掃描到指針就會往下接著尋找,壹直到結束。

Go 語言中 map 是基於 數組和鏈表 的數據結構實現的,通過 優化的拉鏈法 解決哈希沖突,每個 bucket 可以保存 8 對鍵值,在 8 個鍵值對數據後面有壹個 overflow 指針,因為桶中最多只能裝 8 個鍵值對,如果有多余的鍵值對落到了當前桶,那麽就需要再構建壹個桶(稱為溢出桶),通過 overflow 指針鏈接起來。

因為 overflow 指針的緣故,所以無論 map 保存的是什麽,GC 的時候就會把所有的 bmap 掃描壹遍,帶來巨大的 GC 開銷。官方 issues 就有關於這個問題的討論, runtime: Large maps cause significant GC pauses #9477

無腦機翻如下:

如果我們有壹個map [k] v,其中k和v都不包含指針,並且我們想提高掃描性能,則可以執行以下操作。

將“ allOverflow [] unsafe.Pointer”添加到 hmap 並將所有溢出存儲桶存儲在其中。 然後將 bmap 標記為noScan。 這將使掃描非常快,因為我們不會掃描任何用戶數據。

實際上,它將有些復雜,因為我們需要從allOverflow中刪除舊的溢出桶。 而且它還會增加 hmap 的大小,因此也可能需要重新整理數據。

最終官方在 hmap 中增加了 overflow 相關字段完成了上面的優化,這是具體的 commit 地址。

下面看下具體是如何實現的,源碼基於 go1.15,src/cmd/compile/internal/gc/reflect.go 中

通過註釋可以看出,如果 map 中保存的鍵值都不包含指針(通過 Haspointers 判斷),就使用壹個 uintptr 類型代替 bucket 的指針用於溢出桶 overflow 字段,uintptr 類型在 GO 語言中就是個大小可以保存得下指針的整數,不是指針,就相當於實現了 將 bmap 標記為 noScan, GC 的時候就不會遍歷完整個 map 了。隨著不斷的學習,愈發感慨 GO 語言中很多模塊設計得太精妙了。

差不多說清楚了,能力有限,有不對的地方歡迎留言討論,源碼位置還是問的群裏大佬 _

  • 上一篇:萬嬰匯金城幼兒園“月餅盒大變身”親子手工DIY作品大賽
  • 下一篇:有哪個兄弟發點“電流轉速雙閉環直流調速系統設計與仿真”的資料嗎?要是答案不錯的話重謝哦!
  • copyright 2024編程學習大全網