27edb4d9268a3ada0a1704b97d827a98.png
Kubernetes
逃逸分析

静态分析

go buildgo run-gcflags="-m "
package main

import "fmt"

func main() {
   num := GenerateRandomNum()
   fmt.Println(*num)
}

//go:noinline
func GenerateRandomNum() *int {
   tmp := rand.Intn(500)

   return &tmp
}

运行逃逸分析,具体命令如下:

F:hello>go build -gcflags="-m" main.go
# command-line-arguments
.main.go:15:18: inlining call to rand.Intn
.main.go:10:13: inlining call to fmt.Println
.main.go:15:2: moved to heap: tmp
.main.go:10:14: *num escapes to heap
.main.go:10:13: []interface {} literal does not escape
<autogenerated>:1: .this does not escape
<autogenerated>:1: .this does not escape
.main.go:15:2: moved to heap: tmptmp
go build -gcflags="-m -m -m -m -m -W -W" main.go
抽象语法树
ac988c40850a06a6b9f31763b54b3d29.png
源代码的抽象语法树
关于抽象语法树请参考: package ast, ast example
抽象语法树
7da41210a204bec84589fba6db84da07.png
简化的抽象语法树
NAMEADDRDEREF逃逸分析逃逸分析

超过堆栈框架的生命周期

逃逸分析outlive
1c72e1cb7624c1589a2dea1f045e35ee.png
栈内存
num堆
72f360126fb3261252f4e25ad9d10911.png
堆分配
tmp
  • 任何返回的值都会超过函数的生命周期,因为被调用的函数不知道这个值。
  • 在循环外声明的变量在循环内的赋值后会失效。如下面的例子:
package main

func main() {
   var l *int
   for i := 0; i < 10; i++ {
      l = new(int)
      *l = i
   }
   println(*l)
}

./main.go:8:10: new(int) escapes to heap
  • 在闭包外声明的变量在闭包内的赋值后失效。
package main

func main() {
   var l *int
   func() {
      l = new(int)
      *l = 1
   }()
   println(*l)
}

./main.go:10:3: new(int) escapes to heap
逃逸分析

寻址和解引用

构建一个表示寻址/引用次数的加权图,可以让Go优化堆栈分配。让我们分析一个例子来了解它是如何工作的:

package main

func main() {
   n := getAnyNumber()
   println(*n)
}

//go:noinline
func getAnyNumber() *int {
   l := new(int)
   *l = 42

   m := &l
   n := &m
   o := **n

   return o
}
逃逸分析
./main.go:12:10: new(int) escapes to heap

下面是一个简化版的AST代码:

64abdb92cb96fbd790eb1306db80f792.png
简化版 AST
*DEREF1&ADDR1
逃逸分析
variable o has a weight of 0, o has an edge to n
variable n has a weight of 2, n has an edge to m
variable m has a weight of 1, m has an edge to l
variable l has a weight of 0, l has an edge to new(int)
variable new(int) has a weight of -1

每个变量最后的计数为负数,如果超过了当前的栈帧,就会逃逸到堆中。由于返回的值超过了其函数的堆栈框架,并通过其边缘得到了负数,所以分配逃到了堆上。

构建这个图可以让Go了解哪个变量应该留在栈上(尽管它超过了栈的时间)。下面是另一个基本的例子:

func main() {
   num := func1()
   println(*num)
}

//go:noinline
func func1() *int {
   n1 := func2()
   *n1++

   return n1
}

//go:noinline
func func2() *int {
   n2 := rand.Intn(99)

   return &n2
}
./main.go:20:2: moved to heap: n2
n1func1n2