聽說過對 Go map 做 GC 嗎?
在 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
。
這些函式的引數型別是具體的 int64
,mapdelete_fast64
跟原始的 delete
操作一樣的,所以我們來看看 mapdelete
。
mapdelete
長圖預警!!!
大致程式碼分析如上,具體程式碼就留給大家去閱讀了。其實大致過程:
- 防寫,防止併發寫
- 查詢要刪除的
key
是否存在 - 存在則對其標誌做刪除標記
count--
所以你在大面積刪除 key
,實際 map
儲存的 key
是不會刪除的,只是標記當前的key狀態為 empty
。
其實出發點,和 mysql
的標記刪除類似,防止後續會有相同的 key
插入,省去了擴縮容的操作。
但是這個對有些場景是不妥的,如果開發者在未來時間內都不會再插入相同的 key
,很可能會導致 OOM
。
所以針對以上情況,go-zero
開發了 safemap
。下面我們看看 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 支援我們!
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/818/viewspace-2797456/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 深度介紹:? 你聽說過原生 HTML 元件嗎?HTML元件
- 你聽說過“風格指南驅動開發”嗎?
- 聽說,99% 的 Go 程式設計師都被 defer 坑過Go程式設計師
- 基於 JavaScript 的作業系統你聽說過嗎?JavaScript作業系統
- PHP轉Go系列:map對映PHPGo
- 你有使用過JWT嗎?說說你對它的理解JWT
- 聽過“香蕉原則”嗎?
- Java HashMap和Go map原始碼對比JavaHashMapGo原始碼
- 大家聽過MooPHP這款框架嗎?OOPPHP框架
- Go語言之對映(map)快速入門篇Go
- Go(4 [Map])Go
- GO語言————8.6 將 map 的鍵值對調Go
- 說一說近期對微框架做的改動框架
- 人工智慧真的對我們有好處嗎?聽大人物說什麼。人工智慧
- 你有用過HTML5中的datalist標籤嗎?說說你對它的理解HTML
- go 併發 mapGo
- Go中的MapGo
- Go語言mapGo
- go map 和 sliceGo
- Go map相關Go
- 美團一面:專案中使用過Redis嗎?我說用Redis做快取。他對我哦了一聲Redis快取
- YouGov:近半數美國人聽說過OpenAIGoOpenAI
- 你從未聽說過的 JavaScript 早期特性JavaScript
- 終於!Go 1.18 將支援泛型,來聽聽Go 核心技術團隊 Russ Cox怎麼說Go泛型
- 說說你對arguments的理解,它是陣列嗎?陣列
- 聽說創業團隊這樣做才有可能會成功?創業團隊
- javascript 有map嗎JavaScript
- 聽說最近這4款堪稱神器的黑科技APP很熱門,你有用過嗎?APP
- 你信得過AI嗎?聽聽IBM科學家怎麼看AIIBM
- Go GC 機制的大坑GoGC
- Go 1.9 sync.MapGo
- 深入理解 Go MapGo
- Go1.9sync.MapGo
- day06 go mapGo
- Minor GC、Major GC以及Full GC的介紹及對比GC
- map 對比
- GO的GC辣雞回收(一)GoGC
- 大家都在用HTTP/2了,而你還沒聽說過?HTTP