在 Golang# 中的 map 結構,在刪除鍵值對的時候,並且不會真正的刪除,而是標記。那麼隨著鍵值對越來越多,會不會造成大量記憶體浪費呢?
首先答案是會的,很有可能導致 OOM,而且針對這個還有一個討論:github.com/golang/go/issues/20135。大致的意思就是在很大的 map
中,delete
操作沒有真正釋放記憶體而可能導致記憶體 OOM。
所以一般的做法:就是 重建map。而 go-zero
中內建了 safemap
的容器元件。 safemap
在一定程度上可以避免這種情況發生。
那首先我們來看看 go
原生提供的 map
是怎麼刪除的?
1 package main 2 3 func main() { 4 m := make(map[int]string, 9) 5 m[1] = "hello" 6 m[2] = "world" 7 m[3] = "go" 8 9 v, ok := m[1] 10 _, _ = fn(v, ok) 11 12 delete(m, 1) 13 } 14 15 func fn(v string, ok bool) (string, bool) { 16 return v, ok 17 }
測試程式碼如上,我們可以透過go tool compile -S -N -l testmap.go | grep "CALL"
:
0x0071 00113 (test/testmap.go:4) CALL runtime.makemap(SB) 0x0099 00153 (test/testmap.go:5) CALL runtime.mapassign_fast64(SB) 0x00ea 00234 (test/testmap.go:6) CALL runtime.mapassign_fast64(SB) 0x013b 00315 (test/testmap.go:7) CALL runtime.mapassign_fast64(SB) 0x0194 00404 (test/testmap.go:9) CALL runtime.mapaccess2_fast64(SB) 0x01f1 00497 (test/testmap.go:10) CALL "".fn(SB) 0x0214 00532 (test/testmap.go:12) CALL runtime.mapdelete_fast64(SB) 0x0230 00560 (test/testmap.go:7) CALL runtime.gcWriteBarrier(SB) 0x0241 00577 (test/testmap.go:6) CALL runtime.gcWriteBarrier(SB) 0x0252 00594 (test/testmap.go:5) CALL runtime.gcWriteBarrier(SB) 0x025c 00604 (test/testmap.go:3) CALL runtime.morestack_noctxt(SB)
執行第12行的delete
,實際執行的是runtime.mapdelete_fast64
。
這些函數的參數型別是具體的int64
,mapdelete_fast64
跟原始的delete
運算一樣的,所以我們來看看mapdelete
。
長圖預警! ! !
大致程式碼分析如上,具體程式碼就留給大家去閱讀了。其實大致流程:
key
是否存在count--
所以你在大面積刪除key
,實際map
# 儲存的key
是不會刪除的,只是標記目前的key狀態為empty
。
其實出發點,和 mysql
的標記刪除類似,防止後續會有相同的 key
插入,省去了擴縮容的操作。
但這個對有些場景是不妥的,如果開發者在未來時間內都不會再插入相同的 key
,很可能會導致 OOM
。
所以針對以上情況,go-zero
開發了 safemap
。下面我們來看看 safemap
是如何避免這個問題的?
直接從操作safemap
分析為什麼要這麼設計:
newmap
中map
是一個整體,所以key
只能留一份所以為什麼要設定兩個map
就很清楚了:
#dirtyOld
作為儲存主體,如果delete
操作達到閾值,則會觸發遷移。 dirtyNew
作為暫存體,會在到達閾值時,存放部分key/value
所以在遷移操作時,我們需要做的就是:將原先的dirtyOld
清空,儲存的key/value 透過for-range 重新儲存到dirtyNew
,然後將dirtyNew
指向dirtyOld
。
可能會有疑問:不是說
key/value
沒有刪除嗎,只是標記了tophash=empty
其實在
for-range
過程中,會過濾掉tophash 的key
這樣就實作了不需要的key不會被加入dirtyNew
,進而不會影響dirtyOld
。
這其實也就是垃圾回收的年老代和新生代的概念。
更多實作細節,可以查看原始碼!
github.com/tal-tech/go-zero
歡迎使用go-zero 並star支持我們!
以上是你或許聽過對Golang map做GC?的詳細內容。更多資訊請關注PHP中文網其他相關文章!