Go:空花括号对数组初始化内存分配的影响

huangapple go评论98阅读模式
英文:

Go: Effects of empty curly braces on array initialisation memory allocation

问题

我正在玩不同的方法来初始化/声明golang中的数组。我得到了不同的行为/结果。

版本1:

func main() {
    a := [100000000]int64{}
    var i int64
    for i = 0; i < 100000000; i++ {
        a[i] = i
    }
}

生成一个763MB的二进制文件。当我运行它时,几秒钟后就会崩溃,并显示以下消息:

runtime: goroutine stack exceeds 1000000000-byte limit

fatal error: stack overflow

版本2:

func main() {
    var a [100000000]int64
    var i int64
    for i = 0; i < 100000000; i++ {
        a[i] = i
    }
}

生成一个456KB的二进制文件。它在不到一秒的时间内运行完毕。

问题:

有人可以帮助我理解为什么会有这些差异(以及我可能忽略的其他差异)吗?谢谢!

编辑:
运行时间:

我构建了这两个不同的代码片段并运行了编译后的版本,所以编译时间没有被计算在内。不过,第一次运行版本1非常慢。以下是输出结果。

版本1

第一次运行

time ./version1
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow

runtime stack:
runtime.throw(0x2fb42a8e)
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/panic.c:520 +0x69
runtime.newstack()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/stack.c:770 +0x486
runtime.morestack()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/asm_amd64.s:228 +0x61

goroutine 16 [stack growth]:
main.main()
    /Users/ec/repo/offers/lol/version1.go:3 fp=0x2b7b85f50 sp=0x2b7b85f48
runtime.main()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/proc.c:247 +0x11a fp=0x2b7b85fa8 sp=0x2b7b85f50
runtime.goexit()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/proc.c:1445 fp=0x2b7b85fb0 sp=0x2b7b85fa8
created by _rt0_go
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/asm_amd64.s:97 +0x120

goroutine 17 [runnable]:
runtime.MHeap_Scavenger()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/mheap.c:507
runtime.goexit()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/proc.c:1445
./version1  0.00s user 0.10s system 1% cpu 7.799 total

第二次运行

runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow

runtime stack:
runtime.throw(0x2fb42a8e)
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/panic.c:520 +0x69
runtime.newstack()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/stack.c:770 +0x486
runtime.morestack()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/asm_amd64.s:228 +0x61

goroutine 16 [stack growth]:
main.main()
    /Users/ec/repo/offers/lol/version1.go:3 fp=0x2b7b85f50 sp=0x2b7b85f48
runtime.main()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/proc.c:247 +0x11a fp=0x2b7b85fa8 sp=0x2b7b85f50
runtime.goexit()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/proc.c:1445 fp=0x2b7b85fb0 sp=0x2b7b85fa8
created by _rt0_go
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/asm_amd64.s:97 +0x120

goroutine 17 [runnable]:
runtime.MHeap_Scavenger()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/mheap.c:507
runtime.goexit()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/proc.c:1445
./version1  0.00s user 0.10s system 98% cpu 0.102 total

版本2

第一次运行

time ./version2
./version2  0.16s user 0.26s system 99% cpu 0.429 total

第二次运行

time ./version2
./version2  0.17s user 0.25s system 97% cpu 0.421 total
英文:

I was playing with different ways to initialise/declare arrays in golang. I got different behaviours/results.

go version go1.3 darwin/amd64

version 1:

func main() {
    a := [100000000]int64{}
    var i int64
    for i = 0; i &lt; 100000000; i++ {
        a[i] = i
    }
}

Produces a 763MB binary. It crashes after a few seconds when I run it with this message.

runtime: goroutine stack exceeds 1000000000-byte limit

fatal error: stack overflow

version 2:

func main() {
    var a [100000000]int64
    var i int64
    for i = 0; i &lt; 100000000; i++ {
        a[i] = i
    }
}

Produces a 456KB binary. It runs in less than one second.

Question:

Can anybody help me understand why those differences (and other I may have missed) are there? Thanks!

edit:
Running times:

I built the two different snippets and run the compiled versions, so the compilation time wasn't added. The first time I run version1 is extremely slow in comparison though. Here is the output.

go build version1.go
go build version2.go

These are the execution outputs
###version 1

first run

time ./version1
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow

runtime stack:
runtime.throw(0x2fb42a8e)
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/panic.c:520 +0x69
runtime.newstack()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/stack.c:770 +0x486
runtime.morestack()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/asm_amd64.s:228 +0x61

goroutine 16 [stack growth]:
main.main()
    /Users/ec/repo/offers/lol/version1.go:3 fp=0x2b7b85f50 sp=0x2b7b85f48
runtime.main()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/proc.c:247 +0x11a fp=0x2b7b85fa8 sp=0x2b7b85f50
runtime.goexit()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/proc.c:1445 fp=0x2b7b85fb0 sp=0x2b7b85fa8
created by _rt0_go
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/asm_amd64.s:97 +0x120

goroutine 17 [runnable]:
runtime.MHeap_Scavenger()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/mheap.c:507
runtime.goexit()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/proc.c:1445
./version1  0.00s user 0.10s system 1% cpu 7.799 total

second run

runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow

runtime stack:
runtime.throw(0x2fb42a8e)
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/panic.c:520 +0x69
runtime.newstack()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/stack.c:770 +0x486
runtime.morestack()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/asm_amd64.s:228 +0x61

goroutine 16 [stack growth]:
main.main()
    /Users/ec/repo/offers/lol/version1.go:3 fp=0x2b7b85f50 sp=0x2b7b85f48
runtime.main()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/proc.c:247 +0x11a fp=0x2b7b85fa8 sp=0x2b7b85f50
runtime.goexit()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/proc.c:1445 fp=0x2b7b85fb0 sp=0x2b7b85fa8
created by _rt0_go
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/asm_amd64.s:97 +0x120

goroutine 17 [runnable]:
runtime.MHeap_Scavenger()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/mheap.c:507
runtime.goexit()
    /usr/local/Cellar/go/1.3/libexec/src/pkg/runtime/proc.c:1445
./version1  0.00s user 0.10s system 98% cpu 0.102 total

###version 2

first run

time ./version2
./version2  0.16s user 0.26s system 99% cpu 0.429 total

second run

time ./version2
./version2  0.17s user 0.25s system 97% cpu 0.421 total

答案1

得分: 1

在版本1中,你声明了一个字面数组[100000000]int64{},编译器会立即分配内存空间。

在版本2中,你只声明了a的类型为[100000000]int64

当你只有一个变量声明时,在编译过程中无法确定其内容。在版本2中,编译器知道a的类型是[100000000]int64,但内存空间直到运行时才会分配。

当你使用一个字面量时,该精确的内存表示会被写入二进制文件中。这与声明一个string字面量和声明一个string类型的变量是一样的;字符串字面量会直接写入,而变量声明只是一个占位符。

尽管当前的编译器(go 1.3)允许a逃逸到堆上,但字面量数据预期存储在栈帧中。你可以在汇编输出中看到这一点(栈帧大小为800000016):

TEXT	&quot;&quot;.func1+0(SB),$800000016-0

如果你确实需要一个超过栈大小的字面量,你可以将它放在一个全局变量中。下面的代码可以正常执行:

var a = [100000000]int64{1}

func func1() {
	var i int64
	for i = 0; i &lt; 100000000; i++ {
		a[i] = i
	}
}

在这里,我必须初始化a中至少一个值,因为如果字面量等于零值,编译器可能会省略它。

英文:

In Version 1, you're declaring a literal array of [100000000]int64{} which the compiler immediately allocates.

Version 2, you're only declaring the type of a as [100000000]int64.

When you only have a variable declaration, the contents aren't known at that point during compilation. In version 2, the compiler knows that a is of type [100000000]int64, but the memory isn't allocated until runtime.

When you use a literal, that exact memory representation is written into the binary. It works the same as if you declared a string literal vs a variable of type string; the string literal will be written in place, while a variable declaration is only a placeholder.

Even though the current compiler (go 1.3) allows a to escape to the heap, the literal data is expected to live in the stack frame. You can see this in the assembly output (frame size is 800000016):

TEXT	&quot;&quot;.func1+0(SB),$800000016-0

If you actually need a larger literal than what can fit in the stack, you can place it in a global variable. The following executes just fine:

var a = [100000000]int64{1}

func func1() {
	var i int64
	for i = 0; i &lt; 100000000; i++ {
		a[i] = i
	}
}

I did have to initialize at least one value in a here, because it seems that the compiler can elide this literal, if it's equal to the zero value.

答案2

得分: 1

这不是一个实际的答案,但也许有人会觉得这个有用。

在我的情况下,当我尝试对以下结构进行json.Marshal时会出现这种情况:

type Element struct {
    Parent *Element
    Child []*Element
}

其中Parent指向具有当前元素的Child的元素。

所以我只需将"Parent"标记为json:"-",在编组时将其忽略掉。

英文:

It's not actually an answer but maybe someone can find this useful.

In my case this happens when I've try to json.Marshal following structure:

type Element struct {
    Parent *Element
    Child []*Element
}

Where Parent points to element which has current element in Child.

So I just mark "Parent" as json:&quot;-&quot; to be ignored in marshalling.

huangapple
  • 本文由 发表于 2014年9月26日 23:36:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/26063242.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定