Go逃逸分析输出详解
in Go with 0 comment

Go逃逸分析输出详解

in Go with 0 comment

官方社区的解释

"moved to heap" means that a local variable was allocated on the heap
rather than the stack.

"leaking param" means that the memory associated with some parameter
(e.g., if the parameter is a pointer, the memory to which it points)
will escape. This typically means that the caller must allocate that
memory on the heap.

"escapes to heap" means that some value was copied into the heap.
This differs from "moved to heap" in that with "moved to heap" the
variable was allocated in the heap. With "escapes to heap" the value
of some variable was copied, for example when assigning to a variable
of interface type, and that copy forced the value to be copied into a
newly allocated heap slot.

简单来说,
moved to heap是变量自身在堆上进行分配
leaking param是和变量相关的内容在堆上进行分配,如果为指针的话,则是其指向的内容在堆上分配
escapes to heap是和当前变量的一个副本拷贝在堆上进行分配
逃逸分析也会禁止堆到栈的指向,因为函数执行结束后 栈可能被回收,也就导致堆指向未知内容,可能会造成程序崩溃。

如何判断变量是在栈上还是在堆上

注:inheap判断的是b所指向的对象是在栈上还是在堆上

// inheap reports whether b is a pointer into a (potentially dead) heap object.
// It returns false for pointers into mSpanManual spans.
// Non-preemptible because it is used by write barriers.

//go:linkname inheap runtime.inheap
func inheap(b uintptr) bool

先从一个简单的例子入手

package main

import (
	"fmt"
	"unsafe"
	_ "unsafe"
)

type myType struct {
	a int
	b *int
}

//go:noinline
func Test01(m myType) {
	fmt.Println(m.a)
	fmt.Println(m.b)
	println(inheap(uintptr(unsafe.Pointer(&m.a))))
	println(inheap(uintptr(unsafe.Pointer(m.b))))
}

//go:noinline
func Test02(m *myType) {

}

//go:linkname inheap runtime.inheap
func inheap(b uintptr) bool

func main() {
	a := 1
	b := 2
	m := myType{a, &b}
	Test01(m)
}

go build -gcflags='-m'  
./main.go:16:13: inlining call to fmt.Println
./main.go:17:13: inlining call to fmt.Println
./main.go:30:6: can inline main
./main.go:28:13: assuming b is unsafe uintptr
./main.go:15:13: leaking param: m	# m leaking param
./main.go:16:15: m.a escapes to heap	# m.a escapes to heap
./main.go:16:13: []interface {} literal does not escape
./main.go:17:13: []interface {} literal does not escape
./main.go:23:13: m does not escape
./main.go:32:2: moved to heap: b	#b 逃逸到堆上
<autogenerated>:1: .this does not escape

程序运行结果

1
0xc00001c088
false	// m.a不在堆上
true	// *(m.b)在堆上

可以看出b(即*m.b)在堆上分配
m.a escapes to heap,但m.a不在堆上

下面再看一个例子

package main

import (
	"fmt"
	"unsafe"
	_ "unsafe"
)

type myType struct {
	a int
	b *int
}

//go:noinline
func Test02(m *myType) {
	fmt.Println(&m)
	println(inheap(uintptr(unsafe.Pointer(&m))))
}

//go:linkname inheap runtime.inheap
func inheap(b uintptr) bool

func main() {
	a := 1
	b := 2
	m2 := myType{a, &b}
	Test02(&m2)
}

go build -gcflags='-m'  
./main.go:16:13: inlining call to fmt.Println
./main.go:21:13: assuming b is unsafe uintptr
./main.go:15:13: moved to heap: m
./main.go:16:13: []interface {} literal does not escape
./main.go:25:2: moved to heap: b
./main.go:26:2: moved to heap: m2
<autogenerated>:1: .this does not escape
0xc00000e028
true

打印m地址,导致moved to heap: m

package main

import (
	"fmt"
	"unsafe"
	_ "unsafe"
)

type myType struct {
	a int
	b *int
}

//go:noinline
func Test02(m *myType) {
	fmt.Println(m)
	println(inheap(uintptr(unsafe.Pointer(&m))))
	println(inheap(uintptr(unsafe.Pointer(m.b))))
}

//go:linkname inheap runtime.inheap
func inheap(b uintptr) bool

func main() {
	a := 1
	b := 2
	m2 := myType{a, &b}
	Test02(&m2)
}

go build -gcflags='-m'  
./main.go:16:13: inlining call to fmt.Println
./main.go:22:13: assuming b is unsafe uintptr
./main.go:15:13: leaking param: m
./main.go:16:13: []interface {} literal does not escape
./main.go:26:2: moved to heap: b
./main.go:27:2: moved to heap: m2
<autogenerated>:1: .this does not escape
&{1 0xc0000b4008}
false
true

打印m自身,导致leaking param: m,实际上m.b逃逸到堆上

package main

import (
	"fmt"
	"unsafe"
	_ "unsafe"
)

type myType struct {
	a int
	b *int
}

//go:noinline
func Test02(m *myType) {
	fmt.Println(m.b)
	println(inheap(uintptr(unsafe.Pointer(&m))))
	println(inheap(uintptr(unsafe.Pointer(m.b))))
}

//go:linkname inheap runtime.inheap
func inheap(b uintptr) bool

func main() {
	a := 1
	b := 2
	m2 := myType{a, &b}
	Test02(&m2)
}

go build -gcflags='-m'
./main.go:16:13: inlining call to fmt.Println
./main.go:22:13: assuming b is unsafe uintptr
./main.go:15:13: leaking param content: m
./main.go:16:13: []interface {} literal does not escape
./main.go:26:2: moved to heap: b
<autogenerated>:1: .this does not escape

0xc0000b4008
false
true

打印m.b,导致leaking param content: m,实际上*m.b逃逸到堆上
leaking param content: m的含义官方没有给出,推测是说*m中有内容逃逸到堆上