逃逸分析

逃逸分析(Escape Analysis)指的是将变量的内存分配在合适的地方(堆或者栈)。 在函数中申请内存有2种情况:

  • 如果内存分配在栈(stack)上,当函数退出的时候,这部分内存会自然的回收,不需要垃圾回收(GC,Garbage Collection)
  • 如果内存分配在堆(heap)上,函数的执行会交给GC(Garbage Collection)来处理。

此外,Golang与闭包密切相关。

Golang的逃逸分析

Golang的逃逸分析的基本原则是:如果函数返回了变量的引用,那么这个变量就会逃逸。 编译器通过分析代码,决定变量分配的地方:

  • 如果变量在函数外没有被引用,那么优先分配在栈(stack)上。
  • 如果变量在函数外被引用,那么优先分配在堆(heap)上。
PUSHRELEASE

因此,通过内存的逃逸分析,可以尝试将不必要分配在堆上的变量分配在栈上,减少分配堆内存的开销和GC的压力。下面看一下一些逃逸的例子。

指针逃逸

cat.go
package main

type Cat struct {
	Name string
	Age  int
}

//go:noinline
func NewCat(name string, age int) *Cat {
	c := new(Cat) // c will excape to heap
	c.Name = name
	c.Age = age
	return c
}

func main() {
	NewCat("Tom", 5)
}
复制代码

进行逃逸分析

$ go build -gcflags="-m" cat.go
# command-line-arguments
./cat.go:16:6: can inline main
./cat.go:9:13: leaking param: name
./cat.go:10:10: new(Cat) escapes to heap
复制代码
./cat.go:10:10: new(Cat) escapes to heap

动态类型逃逸

package main

import "fmt"

func test() *int {
	s := 3
	return &s
}
func main() {
	x := test()
	fmt.Println(*x)
}
复制代码

编译代码

$ go build -gcflags="-m -l" dynamic.go
# command-line-arguments
./dynamic.go:6:2: moved to heap: s
./dynamic.go:11:13: ... argument does not escape
./dynamic.go:11:14: *x escapes to heap
复制代码
s/dynamic.go:11:14: *x escapes to heapfmt.Println(a ...interface{})fmtinterface{}

slice,map和channel的指针引用

package main

func main() {
	a := make([]*int, 1)
	b := 12
	a[0] = &b
	c := make(map[string]*int)
	d := 14
	c["aaa"] = &d
	e := make(chan *int, 1)
	f := 15
	e <- &f
}
复制代码

编译代码

go run -gcflags "-m -l" main.go
./main.go:7:2: moved to heap: b
./main.go:11:2: moved to heap: d
./main.go:15:2: moved to heap: f
./main.go:6:11: main make([]*int, 1) does not escape
./main.go:10:11: main make(map[string]*int) does not escape
复制代码
bdf

闭包

func Fibonacci() func() int {
	a, b := 0, 1
	return func() int {
		a, b = b, a+b
		return a
	}
}
复制代码

编译

$ go build -gcflags="-m" fib.go
# command-line-arguments
./fib.go:5:9: can inline Fibonacci.func1
./fib.go:4:2: moved to heap: a
./fib.go:4:5: moved to heap: b
./fib.go:5:9: func literal escapes to heap
# command-line-arguments
runtime.main_main·f: function main is undeclared in the main package
复制代码
Fibonacci()

栈容量不足

看下面的代码:

func BigSlice() {
	s := make([]int, 1000, 1000)
	for index, _ := range s {
		s[index] = index
	}
}
func main() {
	BigSlice()
}
复制代码
BigSlice()
$ go build -gcflags="-m" big.go
# command-line-arguments
./big.go:9:6: can inline main
./big.go:4:11: make([]int, 1000, 1000) does not escape
复制代码

这里,没有产生内存逃逸,如果将slice的长度增长10倍,就会产生逃逸。

$ go build -gcflags="-m" big.go
# command-line-arguments
./big.go:9:6: can inline main
./big.go:4:11: make([]int, 10000, 10000) escapes to heap
复制代码

我的公众号:lyp_share