声明非常大的数组并迭代stdin

时间:2016-02-29 08:16:27

标签: memory go

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

这导致内存不断增加。

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

同样,如果我只是迭代stdin - 内存没有增加。

但是(除了为阵列分配的内存之外)还有不断增加。

我通过使用顶级工具查看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. 如果是后者,我该如何调试呢?
  3. 为了更容易复制,这里是pastebin文件的好例子(上面代码的注释部分)和坏的情况(带有未注释的部分)

    wget http://pastebin.com/raw/QfG22xXk -O badcase.go
    yes "1234567890" | go run badcase.go
    
    wget http://pastebin.com/raw/G9xS2fKy -O goodcase.go
    yes "1234567890" | go run goodcase.go
    

1 个答案:

答案 0 :(得分:2)

感谢您Volker的上述评论。我想把这个调试过程作为一个答案。

RES double只是在进程级告诉你内存发生了什么。 GODEBUG =&#34; gctrace = 1&#34;让您更深入地了解如何处理内存。

使用gctrace set的简单运行提供以下内容

top / htop
  

这是什么意思?

正如您所看到的,gc现在还没有被调用一段时间。这意味着reader.ReadString生成的所有垃圾都没有被收集并且是免费的。

  

为什么垃圾收集器没有收集这些垃圾?

来自The go gc

  

相反,我们提供一个叫做GOGC的旋钮。此值控制   堆的总大小相对于可到达对象的大小。   默认值100表示​​总堆大小现在大100%   最后一次之后可到达对象的大小(即两倍)   集合。

由于GOGC未设置 - 默认值为100%。因此,它只会在达到~32GB时收集垃圾。 (因为最初这两个数组为你提供了16GB的堆空间 - 只有当gc触发时才会出现堆加倍)。

  

我该怎么改变?   尝试设置GOGC = 25.

GOGC为25

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。

  

但top / htop显示它稳定在~20 GB而不是计算的16GB。

垃圾收集器没有&#34;&#34;把它还给OS。它有时会使它有效地用于未来。它不必继续从操作系统中取出并回馈 - 在再次询问操作系统之前,额外的4 GB将在其可用空间池中使用。