以下代码声明了两个数组,然后遍历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")
}
为了更容易复制,这里是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
答案 0 :(得分:2)
感谢您Volker的上述评论。我想把这个调试过程作为一个答案。
RES double
只是在进程级告诉你内存发生了什么。 GODEBUG =&#34; gctrace = 1&#34;让您更深入地了解如何处理内存。
使用gctrace set的简单运行提供以下内容
top / htop
这是什么意思?
正如您所看到的,gc现在还没有被调用一段时间。这意味着reader.ReadString生成的所有垃圾都没有被收集并且是免费的。
为什么垃圾收集器没有收集这些垃圾?
相反,我们提供一个叫做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将在其可用空间池中使用。