解释

内存逃逸指的是在函数执行期间分配的变量或对象逃离了函数的作用域,仍然可以被其他部分引用。这种情况下,编译器会将变量或对象分配到堆上,并返回一个指向该内存地址的指针,以便在函数外部仍然能够访问它。

内存逃逸分析的优势是减少了对堆内存的分配和垃圾回收的压力,提高程序的性能。通过将对象分配在栈上而不是堆上,可以减少内存分配和垃圾回收的开销,并且更容易进行优化。

在Go语言中,编译器会自动进行内存逃逸分析,以决定变量或对象是否需要在堆上分配内存。如果一个变量或对象被判定为不逃逸,则会在栈上分配内存;如果被判定为逃逸,则会在堆上分配内存。

分析

1
go build -gcflags=-m -l ./your_program.go
  • -m:启用编译器的内存逃逸分析并输出逃逸信息。内存逃逸分析用于确定变量或对象是否逃逸到堆上分配内存。通过使用-gcflags=”-m”可以查看编译器的逃逸分析结果,以便了解哪些变量或对象逃逸到了堆上。

  • -l:禁用函数体内联。默认情况下,编译器会尝试将函数调用处替换为函数体,以减少函数调用的开销。使用-gcflags=”-l”可以禁用这种内联优化,从而产生更大的、更慢的二进制文件。禁用内联可能会增加函数调用的开销,但也可能提供其他优势,如更好的调试体验。

示例

  • 代码 main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func main() {
c := a()
c()
c()
c()

a() // 不输入任何东西
}

func a() func() int {
i := 0
b := func() int {
i++
fmt.Println(i)
return i
}
return b
}
  • 分析
1
go build -gcflags=-m -l ./main.go
  • 输出
1
2
3
4
5
6
7
8
~/g/s/L/q/q002 ❯❯❯ go build -gcflags="-m -l" ./main.go
# command-line-arguments
./main.go:18:5: moved to heap: i
./main.go:19:10: func literal escapes to heap
./main.go:21:20: ... argument does not escape
./main.go:21:20: i escapes to heap // 这里 `i` 变量就逃逸到堆内存了
./main.go:27:21: s does not escape
./main.go:35:35: string(v) does not escape
  • 理解

a 函数里的返回闭包函数 b 包含了 i 变量, a 函数调用终止后,a 函数栈就回收了,b 函数没法执行,所以编译器会把变量 i 分配到堆内存里去,以便返回的闭包函数 b 能继续使用变量 i。所以在执行 c 的时候输出的是 1,2,3。