GO - 逃避分析

ove*_*nge 3 javascript c python go 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,通过提供syntax(&c)来获得位置访问权限,从而提供更多控制.


在GO中,我的理解是,对于堆/堆栈上的局部变量,取决于在示例代码中应用编译器的转义分析(下面),

func foo() []int {
  // the array lives beyond the call to foo in which it is created
  var a [5]int
  return a[:] // range operator
}
Run Code Online (Sandbox Code Playgroud)

告诉编译器变量a超出了它的范围,所以在堆中分配,而不是堆栈.


题:

变量是否a在堆中分配?

ken*_*ytm 8

在Go中,您应该信任编译器以做出最佳决策.如果可能的话,它将在堆栈上分配内存.另见常见问题:

从正确的角度来看,您不需要知道.Go中的每个变量都存在,只要有对它的引用即可.实现选择的存储位置与语言的语义无关.

存储位置确实会影响编写高效的程序.如果可能,Go编译器将为该函数的堆栈帧中的函数分配本地变量.但是,如果编译器在函数返回后无法证明变量未被引用,则编译器必须在垃圾收集堆上分配变量以避免悬空指针错误.此外,如果局部变量非常大,将它存储在堆而不是堆栈上可能更有意义.

在当前的编译器中,如果变量具有其地址,则该变量是堆上分配的候选变量.但是,基本的转义分析可以识别某些情况,这些变量不会超过函数的返回值并且可以驻留在堆栈上.


如果没有优化(内联),将在堆中分配yesa.我们可以通过传递-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  }
Run Code Online (Sandbox Code Playgroud)
$ 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
Run Code Online (Sandbox Code Playgroud)

我们看到编译器决定inlined.a在第5行和no_inline.b第10行上分配,因为它们都逃避了它们的范围.

但是,在内联之后,编译器注意到a它不再被转义,因此它确定可以再次在堆栈上分配变量(第18行).

结果是变量amaingoroutine的堆栈b上分配,而变量在堆上分配.正如我们从输出中看到的那样,地址b是0x1043xxxx而其他所有地址都是0x1042xxxx.

$ ./1
[5/5]0x10432020
[5/5]0x10429f58
[5/5]0x10429f44
0x10429f40
Run Code Online (Sandbox Code Playgroud)