首頁 >後端開發 >Golang >你或許聽過對Golang map做GC?

你或許聽過對Golang map做GC?

藏色散人
藏色散人轉載
2021-08-13 14:26:462594瀏覽

Golang# 中的 map 結構,在刪除鍵值對的時候,並且不會真正的刪除,而是標記。那麼隨著鍵值對越來越多,會不會造成大量記憶體浪費呢?

首先答案是會的,很有可能導致 OOM,而且針對這個還有一個討論:github.com/golang/go/issues/20135。大致的意思就是在很大的 map 中,delete 操作沒有真正釋放記憶體而可能導致記憶體 OOM。

所以一般的做法:就是 重建map。而 go-zero 中內建了 safemap 的容器元件。 safemap 在一定程度上可以避免這種情況發生。

那首先我們來看看 go 原生提供的 map 是怎麼刪除的?

原生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

這些函數的參數型別是具體的int64mapdelete_fast64 跟原始的delete 運算一樣的,所以我們來看看mapdelete

mapdelete

長圖預警! ! !

你或許聽過對Golang map做GC?

大致程式碼分析如上,具體程式碼就留給大家去閱讀了。其實大致流程:

  1. 寫保護,防止並發寫入
  2. 查詢要刪除的key 是否存在
  3. 存在則對其標誌做刪除標記
  4. count--

所以你在大面積刪除key ,實際map# 儲存的key 是不會刪除的,只是標記目前的key狀態為empty

其實出發點,和 mysql 的標記刪除類似,防止後續會有相同的 key 插入,省去了擴縮容的操作。

但這個對有些場景是不妥的,如果開發者在未來時間內都不會再插入相同的 key ,很可能會導致 OOM

所以針對以上情況,go-zero 開發了 safemap 。下面我們來看看 safemap 是如何避免這個問題的?

safemap

直接從操作safemap 分析為什麼要這麼設計:

你或許聽過對Golang map做GC?

  1. 預設一個刪除閾值,如果觸發會放到一個新預設好的newmap
  2. 兩個map是一個整體,所以key 只能留一份

所以為什麼要設定兩個map 就很清楚了:

  1. #dirtyOld 作為儲存主體,如果delete 操作達到閾值,則會觸發遷移。
  2. dirtyNew 作為暫存體,會在到達閾值時,存放部分key/value

所以在遷移操作時,我們需要做的就是:將原先的dirtyOld 清空,儲存的key/value 透過for-range 重新儲存到dirtyNew,然後將dirtyNew指向dirtyOld

可能會有疑問:不是說key/value 沒有刪除嗎,只是標記了tophash=empty

其實在for-range 過程中,會過濾掉tophash 的key

這樣就實作了不需要的key不會被加入dirtyNew,進而不會影響dirtyOld

你或許聽過對Golang map做GC?

這其實也就是垃圾回收的年老代和新生代的概念。

更多實作細節,可以查看原始碼!

專案位址

github.com/tal-tech/go-zero

歡迎使用go-zero 並star支持我們!

以上是你或許聽過對Golang map做GC?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:learnku.com。如有侵權,請聯絡admin@php.cn刪除