当前位置:首页 > 科技  > 软件

Go 语言中的map和内存泄漏

来源: 责编: 时间:2023-11-21 17:13:42 482观看
导读Map在内存中总是会增长;它不会收缩。因此,如果map导致了一些内存问题,你可以尝试不同的选项,比如强制 Go 重新创建map或使用指针。在 Go 中使用map时,我们需要了解map增长和收缩的一些重要特性。让我们深入探讨这一点,以防

Map在内存中总是会增长;它不会收缩。因此,如果map导致了一些内存问题,你可以尝试不同的选项,比如强制 Go 重新创建map或使用指针。x6T28资讯网——每日最新资讯28at.com

x6T28资讯网——每日最新资讯28at.com

在 Go 中使用map时,我们需要了解map增长和收缩的一些重要特性。让我们深入探讨这一点,以防止可能导致内存泄漏的问题。x6T28资讯网——每日最新资讯28at.com

首先,为了查看这个问题的一个具体例子,让我们设计一个场景,在这个场景中我们将使用以下map:x6T28资讯网——每日最新资讯28at.com

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

每个 m 的值都是一个包含 128 字节的数组。我们将执行以下操作:x6T28资讯网——每日最新资讯28at.com

  • 分配一个空的map。
  • 添加 100 万个元素。
  • 删除所有元素,并运行垃圾回收(GC)。

在每个步骤之后,我们希望打印堆的大小(使用一个 printAlloc 实用函数)。这将展示这个示例在内存方面的行为方式:x6T28资讯网——每日最新资讯28at.com

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)}

我们分配一个空的map,添加 100 万个元素,删除 100 万个元素,然后运行垃圾回收。我们还确保使用 runtime.KeepAlive 保持对map的引用,以防止map被收集。让我们运行这个示例:x6T28资讯网——每日最新资讯28at.com

0 MB   <-- After m is allocated461 MB <-- After we add 1 million elements293 MB <-- After we remove 1 million elements

我们观察到了什么?起初,堆大小很小。然后,在将 100 万个元素添加到map后,它显著增长了。但是,如果我们期望在删除所有元素后堆大小会减小,这并不是 Go 中map的工作方式。最后,尽管 GC 已经收集了所有元素,但堆大小仍然是 293 MB。因此,内存缩小了,但并非我们可能预期的方式。这其中的原理是什么?我们需要深入了解一下 Go 中map的工作原理。x6T28资讯网——每日最新资讯28at.com

map提供了一个无序的键值对集合,其中所有的键都是唯一的。在 Go 中,map基于哈希表数据结构:一个数组,其中每个元素都是指向键值对存储桶的指针,如图1所示。x6T28资讯网——每日最新资讯28at.com

x6T28资讯网——每日最新资讯28at.com

图1 — 哈希表示例,重点关注存储桶 0。x6T28资讯网——每日最新资讯28at.com

每个存储桶都是一个固定大小的数组,包含八个元素。如果要将元素插入已经满了的存储桶(即存储桶溢出),Go 会创建另一个包含八个元素的存储桶,并将前一个存储桶链接到它上。图2显示了一个例子:x6T28资讯网——每日最新资讯28at.com

x6T28资讯网——每日最新资讯28at.com

图2 — 如果存储桶溢出,Go 会分配一个新的存储桶,并将前一个存储桶链接到它上。x6T28资讯网——每日最新资讯28at.com

在底层,Go 中的map是指向 runtime.hmap 结构体的指针。该结构体包含多个字段,其中包括一个 B 字段,表示map中存储桶的数量:x6T28资讯网——每日最新资讯28at.com

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

在添加了100万个元素之后,B 的值等于18,这意味着有 2¹⁸ = 262,144 个存储桶。当我们删除了100万个元素后,B 的值是多少呢?仍然是18。因此,map仍然包含相同数量的存储桶。x6T28资讯网——每日最新资讯28at.com

原因在于map中存储桶的数量是不可缩减的。因此,从map中删除元素不会影响现有存储桶的数量;它只是将存储桶中的槽清零。map只能增长并拥有更多的存储桶;它永远不会缩小。x6T28资讯网——每日最新资讯28at.com

在先前的示例中,我们从461 MB减少到了293 MB,因为元素被收集,但运行垃圾回收并没有影响map本身。即使额外存储桶的数量(因为溢出而创建的存储桶)也保持不变。x6T28资讯网——每日最新资讯28at.com

让我们退一步,讨论map无法缩小的情况何时可能成为问题。想象一下使用 map[int][128]byte 来构建缓存。这个map以每个客户ID(int)为键,保存一个长度为128字节的序列。现在,假设我们想保存最近的1000位客户。map的大小将保持不变,所以我们不必担心map无法缩小的问题。x6T28资讯网——每日最新资讯28at.com

但是,假设我们想要存储一小时的数据。同时,我们的公司决定在黑色星期五进行大促销:在一个小时内,我们可能会有数百万的客户连接到我们的系统。但是在黑色星期五之后的几天,我们的map将包含与高峰期相同数量的存储桶。这就解释了为什么在这种情况下我们可能会遇到内存消耗高却不会显著减少的情况。x6T28资讯网——每日最新资讯28at.com

如果我们不想手动重启服务来清理map消耗的内存量,有哪些解决方案?一种解决方案可以是定期重新创建当前map的副本。例如,每小时我们可以构建一个新map,复制所有元素,并释放先前的map。这种选择的主要缺点是,在复制后直到下一次垃圾回收之前,我们可能会在短时间内消耗两倍于当前内存。x6T28资讯网——每日最新资讯28at.com

另一种解决方案是将map类型更改为存储数组指针:map[int]*[128]byte。这并没有解决我们会有大量存储桶的问题;然而,每个存储桶条目将为值保留指针的大小,而不是128字节(64位系统上为8字节,32位系统上为4字节)。x6T28资讯网——每日最新资讯28at.com

回到原始场景,让我们比较每种map类型在每个步骤后的内存消耗。以下表格显示了比较。x6T28资讯网——每日最新资讯28at.com

Step
x6T28资讯网——每日最新资讯28at.com

map[int][128]bytex6T28资讯网——每日最新资讯28at.com

map[int]*[128]bytex6T28资讯网——每日最新资讯28at.com

分配一个空的 map
x6T28资讯网——每日最新资讯28at.com

0 MB
x6T28资讯网——每日最新资讯28at.com

0 MB
x6T28资讯网——每日最新资讯28at.com

添加100万个元素
x6T28资讯网——每日最新资讯28at.com

461 MB
x6T28资讯网——每日最新资讯28at.com

182 MB
x6T28资讯网——每日最新资讯28at.com

删除所有元素并运行GC
x6T28资讯网——每日最新资讯28at.com

293 MB
x6T28资讯网——每日最新资讯28at.com

38 MB
x6T28资讯网——每日最新资讯28at.com

正如我们所看到的,在删除所有元素后,使用 map[int]*[128]byte 类型所需的内存量明显较少。此外,在这种情况下,由于一些优化措施以减少内存消耗,高峰时期所需的内存量也较少显著。x6T28资讯网——每日最新资讯28at.com

注意:如果键或值超过128字节,Go 将不会直接将其存储在map存储桶中。相反,Go 将存储用于引用键或值的指针。x6T28资讯网——每日最新资讯28at.com

结论

正如我们所见,向map添加 n 个元素,然后删除所有元素意味着在内存中保持相同数量的存储桶。因此,我们必须记住,由于 Go map只能增长,因此其内存消耗也会随之增加。它没有自动化的策略来缩小。如果这导致内存消耗过高,我们可以尝试不同的选项,比如强制 Go 重新创建map或使用指针来检查是否可以进行优化。x6T28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-32441-0.htmlGo 语言中的map和内存泄漏

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: C语言代码:数字雨

下一篇: 五种在 JavaScript 中创建对象的方法

标签:
  • 热门焦点
  • 《英雄联盟》夏季赛总决赛今日开打!JDG对阵LNG首发名单来了 Knight:准备三连冠

    8月5日消息,今日17:00,《英雄联盟》2023LPL夏季赛总决赛将正式开打,由JDG对阵LNG。对两支队伍来说,这场比赛不仅要争夺夏季赛冠军,更要决定谁才是LPL赛区一
  • 0糖0卡0脂 旭日森林仙草乌龙茶优惠:15瓶到手29元

    旭日森林无糖仙草乌龙茶510ml*15瓶平时要卖为79.9元,今日下单领取50元优惠券,到手价为29.9元。产品规格:0糖0卡0脂,添加草本仙草汁,清凉爽口,富含茶多酚,保留
  • 十个可以手动编写的 JavaScript 数组 API

    JavaScript 中有很多API,使用得当,会很方便,省力不少。 你知道它的原理吗? 今天这篇文章,我们将对它们进行一次小总结。现在开始吧。1.forEach()forEach()用于遍历数组接收一参
  • 不容错过的MSBuild技巧,必备用法详解和实践指南

    一、MSBuild简介MSBuild是一种基于XML的构建引擎,用于在.NET Framework和.NET Core应用程序中自动化构建过程。它是Visual Studio的构建引擎,可在命令行或其他构建工具中使用
  • 每天一道面试题-CPU伪共享

    前言:了不起:又到了每天一到面试题的时候了!学弟,最近学习的怎么样啊 了不起学弟:最近学习的还不错,每天都在学习,每天都在进步! 了不起:那你最近学习的什么呢? 了不起学弟:最近在学习C
  • 使用AIGC工具提升安全工作效率

    在日常工作中,安全人员可能会涉及各种各样的安全任务,包括但不限于:开发某些安全工具的插件,满足自己特定的安全需求;自定义github搜索工具,快速查找所需的安全资料、漏洞poc、exp
  • Temu起诉SHEIN,跨境电商战事升级

    来源 | 伯虎财经(bohuFN)作者 | 陈平安日前据外媒报道,拼多多旗下跨境电商平台Temu正对竞争对手SHEIN提起新诉讼,诉状称Shein&ldquo;利用市场支配力量强迫服装厂商与之签订独家
  • ESG的面子与里子

    来源 | 光子星球撰文 | 吴坤谚编辑 | 吴先之三伏大幕拉起,各地高温预警不绝,但处于厄尔尼诺大&ldquo;烤&rdquo;之下的除了众生,还有各大企业发布的ESG报告。ESG是&ldquo;环境保
  • “买真退假” 这种“羊毛”不能薅

    □ 法治日报 记者 王春   □ 本报通讯员 胡佳丽  2020年初,还在上大学的小东加入了一个大学生兼职QQ群。群主&ldquo;七王&rdquo;在群里介绍一些刷单赚
Top