英文:
Escape analysis
问题
在许多编程语言中,局部变量位于调用堆栈中。
在JavaScript/Python中,只有闭包变量位于堆中,因为它们必须在函数调用之后继续存在。
在GO语言中,一些GO类型(如切片类型[]int
)确实引用了内存的其他部分,就像JavaScript/Python一样。
在GO语言中,并非所有类型的变量都持有引用,就像JavaScript/Python一样。
例如,
1)[3]int
类型的变量b
直接存储了一个int
数组,类似于C语言,只是C语言允许使用C语法&b[index]
来访问每个数组元素的位置,以获得更多的控制。
2)int
类型的变量c
直接存储了一个int
值,类似于C语言,只是C语言通过提供语法(&c
)来获得位置访问的更多控制。
在GO语言中,我理解的是,局部变量是在堆还是栈上分配取决于应用编译器的逃逸分析在下面的示例代码中,
func foo() []int {
// 数组在调用foo函数后仍然存在
var a [5]int
return a[:] // 切片操作符
}
这告诉编译器变量a
在其作用域之外仍然存在,因此在堆中分配,而不是栈中。
问题:
变量a
是否在堆中分配?
英文:
In many languages, local variables are located in call stack
In JavaScript/Python, only closure variables are located in heap, because they must live beyond the function calls, they are created.
In GO, some GO types(like slice type []int
) do reference other parts of memory, like JavaScript/Python.
In GO, not all types of variables hold references, like Javascript/Python.
For example,
-
[3]int
type variableb
directly stores an array ofint
's, like C, except that C allows to get access of each array element location using C syntax&b[index]
, for more control -
int
type variablec
directly stores anint
value, like C, except that, C gives more control by providing syntax(&c
) to get location access.
In GO, my understanding is, for local variables to be on heap/stack depends on applying compiler's escape analysis in example code(below),
func foo() []int {
// the array lives beyond the call to foo in which it is created
var a [5]int
return a[:] // range operator
}
that tells the compiler that variable a
lives beyond its scope, so allocate in heap, but not stack.
Question:
Does the variable a
get allocated in heap?
答案1
得分: 14
在Go语言中,你应该相信编译器做出最佳决策。如果可能的话,它会在栈上分配内存。请参阅常见问题解答:
从正确性的角度来看,你不需要知道。在Go语言中,只要有引用指向变量,它就会存在。实现选择的存储位置与语言的语义无关。
存储位置对编写高效程序有影响。在可能的情况下,Go编译器会将函数内部的局部变量分配到该函数的栈帧中。然而,如果编译器无法证明变量在函数返回后不再被引用,那么编译器必须将变量分配到垃圾回收的堆上,以避免悬空指针错误。此外,如果局部变量非常大,将其存储在堆上而不是栈上可能更合理。
在当前的编译器中,如果一个变量的地址被取出,那么该变量有可能被分配到堆上。然而,基本的逃逸分析会识别出一些情况,当这些变量在函数返回后不再存在时,它们可以驻留在栈上。
在没有优化(内联)的情况下,是的,a
将被分配在堆上。我们可以通过传递-gcflags='-m'
来检查逃逸分析(https://play.golang.org/p/l3cZFK5QHO):
$ nl -ba 1.go
1 package main
2
3 func inlined() []int {
4 var a [5]int
5 return a[:]
6 }
7
8 //go:noinline
9 func no_inline() []int {
10 var b [5]int
11 return b[:]
12 }
13
14 func main() {
15 var local_array [5]int
16 var local_var int
17 println(no_inline())
18 println(inlined())
19 println(local_array[:])
20 println(&local_var)
21 }
<!-- -->
$ go build -gcflags='-m' 1.go
# command-line-arguments
./1.go:3: can inline inlined
./1.go:18: inlining call to inlined
./1.go:5: a escapes to heap
./1.go:4: moved to heap: a
./1.go:11: b escapes to heap
./1.go:10: moved to heap: b
./1.go:18: main a does not escape
./1.go:19: main local_array does not escape
./1.go:20: main &local_var does not escape
我们可以看到编译器决定在第5行分配inlined.a
和第10行分配no_inline.b
到堆上,因为它们都逃逸了它们的作用域。
然而,在内联之后,编译器注意到a
不再逃逸,所以它决定将变量再次分配到栈上(第18行)。
结果是变量a
分配在main
goroutine的栈上,而变量b
分配在堆上。从输出中我们可以看到,b
的地址是0x1043xxxx,而其他变量的地址是0x1042xxxx。
$ ./1
[5/5]0x10432020
[5/5]0x10429f58
[5/5]0x10429f44
0x10429f40
英文:
In Go, you are supposed to trust the compiler to make the best decision. It will allocate memory on the stack if possible. See also the FAQ:
> From a correctness standpoint, you don't need to know. Each variable in Go exists as long as there are references to it. The storage location chosen by the implementation is irrelevant to the semantics of the language.
>
> The storage location does have an effect on writing efficient programs. When possible, the Go compilers will allocate variables that are local to a function in that function's stack frame. However, if the compiler cannot prove that the variable is not referenced after the function returns, then the compiler must allocate the variable on the garbage-collected heap to avoid dangling pointer errors. Also, if a local variable is very large, it might make more sense to store it on the heap rather than the stack.
>
> In the current compilers, if a variable has its address taken, that variable is a candidate for allocation on the heap. However, a basic escape analysis recognizes some cases when such variables will not live past the return from the function and can reside on the stack.
Without optimization (inlining), yes a
will be allocated in the heap. We can check the escape analysis by passing -gcflags='-m'
(https://play.golang.org/p/l3cZFK5QHO):
$ nl -ba 1.go
1 package main
2
3 func inlined() []int {
4 var a [5]int
5 return a[:]
6 }
7
8 //go:noinline
9 func no_inline() []int {
10 var b [5]int
11 return b[:]
12 }
13
14 func main() {
15 var local_array [5]int
16 var local_var int
17 println(no_inline())
18 println(inlined())
19 println(local_array[:])
20 println(&local_var)
21 }
<!-- -->
$ go build -gcflags='-m' 1.go
# command-line-arguments
./1.go:3: can inline inlined
./1.go:18: inlining call to inlined
./1.go:5: a escapes to heap
./1.go:4: moved to heap: a
./1.go:11: b escapes to heap
./1.go:10: moved to heap: b
./1.go:18: main a does not escape
./1.go:19: main local_array does not escape
./1.go:20: main &local_var does not escape
We see that the compiler decided to allocate inlined.a
on line 5 and no_inline.b
on line 10 on the heap, because they both escape their scope.
However, after inlining, the compiler noticed that the a
does not escape any more, so it determines the variable can be allocated on the stack again (line 18).
The result is that the variable a
is allocated on the main
goroutine's stack, while the variable b
is allocated on the heap. As we can see from the output, the address of b
is on 0x1043xxxx while all other are on 0x1042xxxx.
$ ./1
[5/5]0x10432020
[5/5]0x10429f58
[5/5]0x10429f44
0x10429f40
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论