声明非常大的数组并迭代 stdin 时,内存持续增加

以下代码声明了两个数组,然后遍历 stdin(只是盲目地遍历文件 - 不与数组交互)。

这导致内存不断增加。

但是,如果我只是声明两个数组并睡眠 - 内存不会增加。

同样,如果我只是遍历 stdin - 内存不会增加。

但是一起(除了为数组分配的内存)有一个持续的增加。

我通过使用 top 工具查看 RES 内存来衡量这一点。

我已经注释掉了中的前几行,func doSomething()以表明注释时没有内存增加。取消注释行并运行将导致增加。

注意:这是在 1.4.2、1.5.3 和 1.6 上运行的

注意:您需要在至少具有 16GB RAM 的机器上重新创建它,因为我仅在 10 亿的阵列大小上观察到它。

package main


import (

    "bufio"

    "fmt"

    "io"

    "os"

)


type MyStruct struct {

    arr1 []int

    arr2 []int

}


func (ms *MyStruct) Init(size int, arr1 []int, arr2 []int) error {

    fmt.Printf("initializing mystruct arr1...\n")

    ms.arr1 = arr1

    if ms.arr1 == nil {

        ms.arr1 = make([]int, size, size)

    }

    fmt.Printf("initializing mystruct arr2...\n")

    ms.arr2 = arr2

    if ms.arr2 == nil {

        ms.arr2 = make([]int, size, size)

    }

    fmt.Printf("done initializing ...\n")

    for i := 0; i < size; i++ {

        ms.arr1[i] = 0

        ms.arr2[i] = 0

    }

    return nil

}


func doSomething() error {

    fmt.Printf("starting...\n")

    fmt.Printf("allocating\n")

    /* NOTE WHEN UNCOMMENTED CAUSES MEMORY INCREASE 

    ms := &MyStruct{}

    size := 1000000000

    ms.Init(size, nil, nil)

    */


    fmt.Printf("finished allocating..%d %d\n", len(ms.arr1), len(ms.arr2))


    fmt.Printf("reading from stdin...\n")

    reader := bufio.NewReader(os.Stdin)


    var line string

    var readErr error

    var lineNo int = 0

    for {

        if lineNo%1000000 == 0 {

            fmt.Printf("read %d lines...\n", lineNo)

        }

        lineNo++


        line, readErr = reader.ReadString('\n')

        if readErr != nil {

            fmt.Printf("break at %s\n", line)

            break

        }

    }


    if readErr == io.EOF {

        readErr = nil

    }


    if readErr != nil {

        return readErr

    }


    return nil

}


func main() {

    if err := doSomething(); err != nil {

        panic(err)

    }

    fmt.Printf("done...\n")

}

  1. 这是我的代码的问题吗?还是 go 系统做了一些意想不到的事情?

  2. 如果是后者,我该如何调试呢?



胡子哥哥
浏览 137回答 1
1回答

精慕HU

我想捕捉调试这个过程作为答案。REStop / htop只是在进程级别告诉您内存的情况。GODEBUG="gctrace=1" 让您更深入地了解内存的处理方式。使用 gctrace set 的简单运行给出以下结果root@localhost ~ # yes "12345678901234567890123456789012" | GODEBUG="gctrace=1" go run badcase.gostarting...allocatinginitializing mystruct arr1...initializing mystruct arr2...gc 1 @0.050s 0%: 0.19+0.23+0.068 ms clock, 0.58+0.016/0.16/0.25+0.20 ms cpu, 7629->7629->7629 MB, 7630 MB goal, 8 Pdone initializing ...gc 2 @0.100s 0%: 0.070+2515+0.23 ms clock, 0.49+0.025/0.096/0.24+1.6finished allocating..1000000000 1000000000&nbsp;ms cpu, 15258->15258reading from stdin...->15258 MB, 15259read 0 lines...&nbsp;MB goal, 8 Pgc 3 @2.620s 0%: 0.009+0.32+0.23 ms clock, 0.072+0/0.20/0.11+1.8 ms cpu, 15259->15259->15258 MB, 30517 MB goal, 8 Pread 1000000 lines...read 2000000 lines...read 3000000 lines...read 4000000 lines.......read 51000000 lines...read 52000000 lines...read 53000000 lines...read 54000000 lines...这是什么意思 ?如您所见,gc 已经有一段时间没有被调用了。这意味着所有从 reader.ReadString 生成的垃圾还没有被收集和释放。为什么垃圾收集器不收集这些垃圾?来自go gc相反,我们提供了一个称为 GOGC 的旋钮。该值控制相对于可达对象大小的堆的总大小。默认值 100 意味着总堆大小现在比上次收集后可达对象的大小大 100%(即两倍)。由于未设置 GOGC - 默认值为 100%。因此,只有在达到 ~32GB 时它才会收集垃圾。(因为最初这两个数组为您提供 16GB 的堆空间 - 只有当堆加倍时才会触发 gc )。我怎样才能改变这个?尝试设置 GOGC=25。GOGC 为 25root@localhost ~ # yes "12345678901234567890123456789012" | GODEBUG="gctrace=1" GOGC=25 go run badcase.gostarting...allocatinginitializing mystruct arr1...initializing mystruct arr2...gc 1 @0.051s 0%: 0.14+0.30+0.11 ms clock, 0.42+0.016/0.31/0.094+0.35 ms cpu, 7629->7629->7629 MB, 7630 MB goal, 8 Pdone initializing ...finished allocating..1000000000 1000000000gc 2 @0.102s reading from stdin...12%: 0.058+2480+0.26 ms clock, 0.40+0.022/2480/0.10+1.8 ms cpu, 15258->15258->15258 MB, 15259 MB goal, 8 Pread 0 lines...gc 3 @2.584s 12%: 0.009+0.20+0.22 ms clock, 0.075+0/0.24/0.046+1.8 ms cpu, 15259->15259->15258 MB, 19073 MB goal, 8 Pread 1000000 lines...read 2000000 lines...read 3000000 lines...read 4000000 lines.......read 19000000 lines...read 20000000 lines...gc 4 @6.539s 4%: 0.019+2.3+0.23 ms clock, 0.15+0/2.1/12+1.8 ms cpu, 17166->17166->15258 MB, 19073 MB goal, 8 P如您所见,触发了另一个 gc。但是 top/htop 显示它稳定在 ~20 GB 而不是计算的 16 GB。垃圾收集器不必“必须”将其返回给操作系统。它有时会保留它以供将来有效使用。它不必不断从操作系统中获取并回馈 - 在再次询问操作系统之前,额外的 4 GB 位于可用空间池中以供使用。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go