英文:
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 < 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 < 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 "".func1+0(SB),$800000016-0
如果你确实需要一个超过栈大小的字面量,你可以将它放在一个全局变量中。下面的代码可以正常执行:
var a = [100000000]int64{1}
func func1() {
var i int64
for i = 0; i < 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 "".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 < 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:"-"
to be ignored in marshalling.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论