考虑下面的代码,其中我分配了4000个数组,每个数组的长度为100k:

    parentMap := make(map[int][100_000]int)
    for i := 0; i < 4000; i++ {
        parentMap[i] = [100_000]int{}
        time.Sleep(3 * time.Millisecond)
    }

如果我在本地运行它,并分析它的内存使用情况,它开始使用2 GB的内存.

现在,如果我们稍微更改代码以使用数组片段(但长度也是100k),如下所示:

    parentMap := make(map[int][]int)
    for i := 0; i < 4000; i++ {
        parentMap[i] = make([]int, 100_000)
        time.Sleep(3 * time.Millisecond)
    }

在我的机器上,内存峰值约为73MB.Why is this?

我认为这两个代码片段使用的内存大致相同,原因如下:

  • 在这两种情况下,Go运行时都将在堆上分配值parentMap.Go之所以这样做,是因为如果它在堆栈上分配了这些值,那么当前函数一旦超出作用域,parentMap的值就会全部清除.
  • 因此,第一个代码片段直接在堆上分配4karray.
  • 并且,第二个代码段在堆上分配4k切片标头.每个片标头都有一个指向大小为100k的唯一数组(也在堆上)的指针.
  • 在这两种情况下,大小为100k的堆上都有4k个array.因此,在这两种情况下都应该使用大致相同的内存量.

我读到:https://go.dev/blog/slices-intro.但是找不到可以解释这一点的实现细节.

推荐答案

带有切片的版本可能受益于懒惰的分配.任何东西都不会try 写入这些片的数据缓冲区,因此操作系统可以自由地不为这些缓冲区实际分配内存,直到有东西真正try 写入为止.(操作系统也可以延迟地对缓冲区进行零初始化,因此不会强制分配.)

同时,包含数组的版本需要将数组实际复制到映射中,这意味着实际执行写入.即使写入的值都是零,它们仍然是写入,因此操作系统必须为要写入的数据实际分配内存.

try 将数据写入这些切片,切片版本也应该占用GB的内存.(我认为每页内存一个值应该足够了,但只用1填充切片可能会更容易.)

Go相关问答推荐

如何使用Gorilla WebSockets实现Http.Hijacker&;alexedwards/scs/v2

如何给杜松子wine 的路由加上一个名字,比如Laravel ?

golang有int32溢出吗?

Docker Compose Health Check未退出,错误为无法启动

提供的client_secret与此帐户上任何关联的SetupIntent都不匹配

Kafka架构注册表-Broker:Broker无法验证记录

Golang text/template中的startswith函数 - 入门教程

Caddy服务器try 打开端口80而不是8090.

linter 警告:返回值被忽略

如何从 Go 中的 `HijackedResponse` 中删除 Cursor Position ANSI 转义码?

拆分文本并按空格获取字符串数组,如果文本长度超过 500,则获取字符串数组

在 Go 中读取数字行

具有近似约束的函数值导致的实例化失败

如何 Select 前 N 个元素 Gin-Gorm

如何排除溢出矩阵的坐标

在 Go 中,为什么 exec.Command() 失败但 os.StartProcess() 成功启动winget.exe?

在 VSCode 中使用命令行参数调试 Go 测试

泛型:对具有返回自身的函数的类型的约束

关于GO的几个问题

如何迭代在泛型函数中传递的片的并集?