为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
胡子哥哥 2021-12-20 10:38:05
以下代码声明了两个数组,然后遍历 stdin(只是盲目地遍历文件 - 不与数组交互)。这导致内存不断增加。但是,如果我只是声明两个数组并睡眠 - 内存不会增加。同样,如果我只是遍历 stdin - 内存不会增加。但是一起(除了为数组分配的内存)有一个持续的增加。我通过使用 top 工具查看 RES 内存来衡量这一点。我已经注释掉了中的前几行,func doSomething()以表明注释时没有内存增加。取消注释行并运行将导致增加。注意:这是在 1.4.2、1.5.3 和 1.6 上运行的注意:您需要在至少具有 16GB RAM 的机器上重新创建它,因为我仅在 10 亿的阵列大小上观察到它。package mainimport (    "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")}这是我的代码的问题吗?还是 go 系统做了一些意想不到的事情?如果是后者,我该如何调试呢?
查看完整描述

1 回答

?
精慕HU

TA贡献1845条经验 获得超8个赞

我想捕捉调试这个过程作为答案。


REStop / htop只是在进程级别告诉您内存的情况。GODEBUG="gctrace=1" 让您更深入地了解内存的处理方式。


使用 gctrace set 的简单运行给出以下结果


root@localhost ~ # yes "12345678901234567890123456789012" | GODEBUG="gctrace=1" go run badcase.go

starting...

allocating

initializing 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 P

done 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

 ms cpu, 15258->15258reading from stdin...

->15258 MB, 15259read 0 lines...

 MB goal, 8 P

gc 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 P


read 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 为 25


root@localhost ~ # yes "12345678901234567890123456789012" | GODEBUG="gctrace=1" GOGC=25 go run badcase.go

starting...

allocating

initializing 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 P

done initializing ...

finished allocating..1000000000 1000000000

gc 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 P

read 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 P

read 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 位于可用空间池中以供使用。


查看完整回答
反对 回复 2021-12-20
  • 1 回答
  • 0 关注
  • 136 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信