<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    聽說(shuō)過(guò)對(duì) Go map 做 GC 嗎?

    共 3891字,需瀏覽 8分鐘

     ·

    2021-09-05 23:38

    在 Golang 中的 map 結(jié)構(gòu),在刪除鍵值對(duì)的時(shí)候,并不會(huì)真正的刪除,而是標(biāo)記。那么隨著鍵值對(duì)越來(lái)越多,會(huì)不會(huì)造成大量?jī)?nèi)存浪費(fèi)?

    首先答案是會(huì)的,很有可能導(dǎo)致 OOM,而且針對(duì)這個(gè)還有一個(gè)討論:https://github.com/golang/go/issues/20135。大致的意思就是在很大的 map 中,delete 操作沒有真正釋放內(nèi)存而可能導(dǎo)致內(nèi)存 OOM。

    所以一般的做法:就是 重建 map。而 go-zero 中內(nèi)置了 safemap 的容器組件。safemap 在一定程度上可以避免這種情況發(fā)生。

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

    原生map刪除

    1  package main
    2
    3  func main() {
    4      m := make(map[int]string9)
    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) (stringbool) {
    16     return v, ok
    17 }

    測(cè)試代碼如上,我們可以通過(guò) 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)

    執(zhí)行第12行的 delete,實(shí)際執(zhí)行的是 runtime.mapdelete_fast64。

    這些函數(shù)的參數(shù)類型是具體的 int64,mapdelete_fast64 跟原始的 delete 操作一樣的,所以我們來(lái)看看 mapdelete。

    mapdelete

    長(zhǎng)圖預(yù)警?。?!

    大致代碼分析如上,具體代碼就留給大家去閱讀了。其實(shí)大致過(guò)程:

    1. 寫保護(hù),防止并發(fā)寫
    2. 查詢要?jiǎng)h除的 key 是否存在
    3. 存在則對(duì)其標(biāo)志做刪除標(biāo)記
    4. count--

    所以你在大面積刪除 key ,實(shí)際 map 存儲(chǔ)的 key 是不會(huì)刪除的,只是標(biāo)記當(dāng)前的key狀態(tài)為 empty。

    其實(shí)出發(fā)點(diǎn),和 mysql 的標(biāo)記刪除類似,防止后續(xù)會(huì)有相同的 key 插入,省去了擴(kuò)縮容的操作。

    但是這個(gè)對(duì)有些場(chǎng)景是不妥的,如果開發(fā)者在未來(lái)時(shí)間內(nèi)都不會(huì)再插入相同的 key ,很可能會(huì)導(dǎo)致 OOM。

    所以針對(duì)以上情況,go-zero 開發(fā)了 safemap 。下面我們看看 safemap 是如何避免這個(gè)問題的?

    safemap

    直接從操作 safemap 中分析為什么要這么設(shè)計(jì):

    1. 預(yù)設(shè)一個(gè) 刪除閾值,如果觸發(fā)會(huì)放到一個(gè)新預(yù)設(shè)好的 newmap
    2. 兩個(gè) map 是一個(gè)整體,所以 key 只能留一份

    所以為什么要設(shè)置兩個(gè) map 就很清楚了:

    1. dirtyOld 作為存儲(chǔ)主體,如果 delete 操作達(dá)到閾值,則會(huì)觸發(fā)遷移。
    2. dirtyNew 作為暫存體,會(huì)在到達(dá)閾值時(shí),存放部分 key/value

    所以在遷移操作時(shí),我們需要做的就是:將原先的 dirtyOld 清空,存儲(chǔ)的 key/value 通過(guò) for-range 重新存儲(chǔ)到 dirtyNew,然后將 dirtyNew 指向 dirtyOld。

    可能會(huì)有疑問:不是說(shuō) key/value 沒有刪除嗎,只是標(biāo)記了 tophash=empty

    其實(shí)在 for-range 過(guò)程中,會(huì)過(guò)濾掉 tophash <= emptyOne 的 key

    這樣就實(shí)現(xiàn)了不需要的 key 不會(huì)被加入到 dirtyNew,進(jìn)而不會(huì)影響 dirtyOld

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

    更多實(shí)現(xiàn)細(xì)節(jié),可以查看源碼!

    項(xiàng)目地址

    https://github.com/tal-tech/go-zero

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



    往期推薦


    我是 polarisxu,北大碩士畢業(yè),曾在 360 等知名互聯(lián)網(wǎng)公司工作,10多年技術(shù)研發(fā)與架構(gòu)經(jīng)驗(yàn)!2012 年接觸 Go 語(yǔ)言并創(chuàng)建了 Go 語(yǔ)言中文網(wǎng)!著有《Go語(yǔ)言編程之旅》、開源圖書《Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)》等。


    堅(jiān)持輸出技術(shù)(包括 Go、Rust 等技術(shù))、職場(chǎng)心得和創(chuàng)業(yè)感悟!歡迎關(guān)注「polarisxu」一起成長(zhǎng)!也歡迎加我微信好友交流:gopherstudio


    瀏覽 35
    點(diǎn)贊
    評(píng)論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報(bào)
    評(píng)論
    圖片
    表情
    推薦
    點(diǎn)贊
    評(píng)論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報(bào)

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    大香蕉尹人在线 | 五月天视频网站 | 免费毛片视频网站在线观看 | 91乱子伦国产乱子伦www.sz-sd.cn | www.豆花社区成人 |