偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Go 語(yǔ)言中的map和內(nèi)存泄漏

開(kāi)發(fā) 后端
在 Go 中使用map時(shí),我們需要了解map增長(zhǎng)和收縮的一些重要特性。讓我們深入探討這一點(diǎn),以防止可能導(dǎo)致內(nèi)存泄漏的問(wèn)題。

Map在內(nèi)存中總是會(huì)增長(zhǎng);它不會(huì)收縮。因此,如果map導(dǎo)致了一些內(nèi)存問(wèn)題,你可以嘗試不同的選項(xiàng),比如強(qiáng)制 Go 重新創(chuàng)建map或使用指針。

在 Go 中使用map時(shí),我們需要了解map增長(zhǎng)和收縮的一些重要特性。讓我們深入探討這一點(diǎn),以防止可能導(dǎo)致內(nèi)存泄漏的問(wèn)題。

首先,為了查看這個(gè)問(wèn)題的一個(gè)具體例子,讓我們?cè)O(shè)計(jì)一個(gè)場(chǎng)景,在這個(gè)場(chǎng)景中我們將使用以下map:

m := make(map[int][128]byte)

每個(gè) m 的值都是一個(gè)包含 128 字節(jié)的數(shù)組。我們將執(zhí)行以下操作:

  • 分配一個(gè)空的map。
  • 添加 100 萬(wàn)個(gè)元素。
  • 刪除所有元素,并運(yùn)行垃圾回收(GC)。

在每個(gè)步驟之后,我們希望打印堆的大?。ㄊ褂靡粋€(gè) printAlloc 實(shí)用函數(shù))。這將展示這個(gè)示例在內(nèi)存方面的行為方式:

func main() {
    n := 1_000_000
    m := make(map[int][128]byte)
    printAlloc()

    for i := 0; i < n; i++ { // Adds 1 million elements
        m[i] = [128]byte{}
    }
    printAlloc()

    for i := 0; i < n; i++ { // Deletes 1 million elements
        delete(m, i)
    }

    runtime.GC() // Triggers a manual GC
    printAlloc()
    runtime.KeepAlive(m) // Keeps a reference to m so that the map isn’t collected
}

func printAlloc() {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    fmt.Printf("%d KB\n", m.Alloc/1024)
}

我們分配一個(gè)空的map,添加 100 萬(wàn)個(gè)元素,刪除 100 萬(wàn)個(gè)元素,然后運(yùn)行垃圾回收。我們還確保使用 runtime.KeepAlive 保持對(duì)map的引用,以防止map被收集。讓我們運(yùn)行這個(gè)示例:

0 MB   <-- After m is allocated
461 MB <-- After we add 1 million elements
293 MB <-- After we remove 1 million elements

我們觀察到了什么?起初,堆大小很小。然后,在將 100 萬(wàn)個(gè)元素添加到map后,它顯著增長(zhǎng)了。但是,如果我們期望在刪除所有元素后堆大小會(huì)減小,這并不是 Go 中map的工作方式。最后,盡管 GC 已經(jīng)收集了所有元素,但堆大小仍然是 293 MB。因此,內(nèi)存縮小了,但并非我們可能預(yù)期的方式。這其中的原理是什么?我們需要深入了解一下 Go 中map的工作原理。

map提供了一個(gè)無(wú)序的鍵值對(duì)集合,其中所有的鍵都是唯一的。在 Go 中,map基于哈希表數(shù)據(jù)結(jié)構(gòu):一個(gè)數(shù)組,其中每個(gè)元素都是指向鍵值對(duì)存儲(chǔ)桶的指針,如圖1所示。

圖1 — 哈希表示例,重點(diǎn)關(guān)注存儲(chǔ)桶 0。

每個(gè)存儲(chǔ)桶都是一個(gè)固定大小的數(shù)組,包含八個(gè)元素。如果要將元素插入已經(jīng)滿(mǎn)了的存儲(chǔ)桶(即存儲(chǔ)桶溢出),Go 會(huì)創(chuàng)建另一個(gè)包含八個(gè)元素的存儲(chǔ)桶,并將前一個(gè)存儲(chǔ)桶鏈接到它上。圖2顯示了一個(gè)例子:

圖2 — 如果存儲(chǔ)桶溢出,Go 會(huì)分配一個(gè)新的存儲(chǔ)桶,并將前一個(gè)存儲(chǔ)桶鏈接到它上。

在底層,Go 中的map是指向 runtime.hmap 結(jié)構(gòu)體的指針。該結(jié)構(gòu)體包含多個(gè)字段,其中包括一個(gè) B 字段,表示map中存儲(chǔ)桶的數(shù)量:

type hmap struct {
    B uint8 // log_2 of # of buckets
            // (can hold up to loadFactor * 2^B items)
    // ...
}

在添加了100萬(wàn)個(gè)元素之后,B 的值等于18,這意味著有 21? = 262,144 個(gè)存儲(chǔ)桶。當(dāng)我們刪除了100萬(wàn)個(gè)元素后,B 的值是多少呢?仍然是18。因此,map仍然包含相同數(shù)量的存儲(chǔ)桶。

原因在于map中存儲(chǔ)桶的數(shù)量是不可縮減的。因此,從map中刪除元素不會(huì)影響現(xiàn)有存儲(chǔ)桶的數(shù)量;它只是將存儲(chǔ)桶中的槽清零。map只能增長(zhǎng)并擁有更多的存儲(chǔ)桶;它永遠(yuǎn)不會(huì)縮小。

在先前的示例中,我們從461 MB減少到了293 MB,因?yàn)樵乇皇占?,但運(yùn)行垃圾回收并沒(méi)有影響map本身。即使額外存儲(chǔ)桶的數(shù)量(因?yàn)橐绯龆鴦?chuàng)建的存儲(chǔ)桶)也保持不變。

讓我們退一步,討論map無(wú)法縮小的情況何時(shí)可能成為問(wèn)題。想象一下使用 map[int][128]byte 來(lái)構(gòu)建緩存。這個(gè)map以每個(gè)客戶(hù)ID(int)為鍵,保存一個(gè)長(zhǎng)度為128字節(jié)的序列?,F(xiàn)在,假設(shè)我們想保存最近的1000位客戶(hù)。map的大小將保持不變,所以我們不必?fù)?dān)心map無(wú)法縮小的問(wèn)題。

但是,假設(shè)我們想要存儲(chǔ)一小時(shí)的數(shù)據(jù)。同時(shí),我們的公司決定在黑色星期五進(jìn)行大促銷(xiāo):在一個(gè)小時(shí)內(nèi),我們可能會(huì)有數(shù)百萬(wàn)的客戶(hù)連接到我們的系統(tǒng)。但是在黑色星期五之后的幾天,我們的map將包含與高峰期相同數(shù)量的存儲(chǔ)桶。這就解釋了為什么在這種情況下我們可能會(huì)遇到內(nèi)存消耗高卻不會(huì)顯著減少的情況。

如果我們不想手動(dòng)重啟服務(wù)來(lái)清理map消耗的內(nèi)存量,有哪些解決方案?一種解決方案可以是定期重新創(chuàng)建當(dāng)前map的副本。例如,每小時(shí)我們可以構(gòu)建一個(gè)新map,復(fù)制所有元素,并釋放先前的map。這種選擇的主要缺點(diǎn)是,在復(fù)制后直到下一次垃圾回收之前,我們可能會(huì)在短時(shí)間內(nèi)消耗兩倍于當(dāng)前內(nèi)存。

另一種解決方案是將map類(lèi)型更改為存儲(chǔ)數(shù)組指針:map[int]*[128]byte。這并沒(méi)有解決我們會(huì)有大量存儲(chǔ)桶的問(wèn)題;然而,每個(gè)存儲(chǔ)桶條目將為值保留指針的大小,而不是128字節(jié)(64位系統(tǒng)上為8字節(jié),32位系統(tǒng)上為4字節(jié))。

回到原始場(chǎng)景,讓我們比較每種map類(lèi)型在每個(gè)步驟后的內(nèi)存消耗。以下表格顯示了比較。

Step

map[int][128]byte

map[int]*[128]byte

分配一個(gè)空的 map

0 MB

0 MB

添加100萬(wàn)個(gè)元素

461 MB

182 MB

刪除所有元素并運(yùn)行GC

293 MB

38 MB

正如我們所看到的,在刪除所有元素后,使用 map[int]*[128]byte 類(lèi)型所需的內(nèi)存量明顯較少。此外,在這種情況下,由于一些優(yōu)化措施以減少內(nèi)存消耗,高峰時(shí)期所需的內(nèi)存量也較少顯著。

注意:如果鍵或值超過(guò)128字節(jié),Go 將不會(huì)直接將其存儲(chǔ)在map存儲(chǔ)桶中。相反,Go 將存儲(chǔ)用于引用鍵或值的指針。

結(jié)論

正如我們所見(jiàn),向map添加 n 個(gè)元素,然后刪除所有元素意味著在內(nèi)存中保持相同數(shù)量的存儲(chǔ)桶。因此,我們必須記住,由于 Go map只能增長(zhǎng),因此其內(nèi)存消耗也會(huì)隨之增加。它沒(méi)有自動(dòng)化的策略來(lái)縮小。如果這導(dǎo)致內(nèi)存消耗過(guò)高,我們可以嘗試不同的選項(xiàng),比如強(qiáng)制 Go 重新創(chuàng)建map或使用指針來(lái)檢查是否可以進(jìn)行優(yōu)化。

責(zé)任編輯:趙寧寧 來(lái)源: 技術(shù)的游戲
相關(guān)推薦

2012-06-15 09:56:40

2023-11-30 08:09:02

Go語(yǔ)言

2022-10-10 11:37:14

Gomap內(nèi)存

2023-12-30 18:35:37

Go識(shí)別應(yīng)用程序

2014-01-14 09:10:53

GoHTTP內(nèi)存泄漏

2024-04-07 11:33:02

Go逃逸分析

2021-07-15 23:18:48

Go語(yǔ)言并發(fā)

2023-12-21 07:09:32

Go語(yǔ)言任務(wù)

2023-10-09 07:14:42

panicGo語(yǔ)言

2022-07-19 12:25:29

Go

2023-07-29 15:03:29

2021-06-08 07:45:44

Go語(yǔ)言優(yōu)化

2023-01-12 08:52:50

GoroutinesGo語(yǔ)言

2025-05-30 01:55:00

go語(yǔ)言Redis

2024-12-05 08:58:47

2023-12-25 09:58:25

sync包Go編程

2024-05-10 08:36:40

Go語(yǔ)言對(duì)象

2024-01-08 07:02:48

數(shù)據(jù)設(shè)計(jì)模式

2021-07-13 06:44:04

Go語(yǔ)言數(shù)組

2025-03-27 00:45:00

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)