Go语言变量逃逸分析
在讨论变量生命周期之前,先来了解下计算机组成里两个非常重要的概念:堆和栈。
变量和栈有什么关系
栈可用于内存分配,栈的分配和回收速度非常快func calc(a, b int) int { var c int c = a * b var x int x = c * 10 return x }
代码说明如下:
第 1 行,传入 a、b 两个整型参数。 第 2 行,声明整型变量 c,运行时,c 会分配一段内存用以存储 c 的数值。 第 3 行,将 a 和 b 相乘后赋值给 c。 第 5 行,声明整型变量 x,x 也会被分配一段内存。 第 6 行,让 c 乘以 10 后赋值给变量 x。 第 8 行,返回 x 的值。上面的代码在没有任何优化的情况下,会进行变量 c 和 x 的分配过程。Go语言默认情况下会将 c 和 x 分配在栈上,这两个变量在 calc() 函数退出时就不再使用,函数结束时,保存 c 和 x 的栈内存再出栈释放内存,整个分配内存的过程通过栈的分配和回收都会非常迅速。
什么是堆
堆在内存分配中类似于往一个房间里摆放各种家具,家具的尺寸有大有小,分配内存时,需要找一块足够装下家具的空间再摆放家具。堆分配内存和栈分配内存相比,堆适合不可预知大小的内存分配。但是为此付出的代价是分配速度较慢,而且会形成内存碎片。
变量逃逸(Escape Analysis)——自动决定变量分配方式,提高运行效率
在 C/C++ 语言中,需要开发者自己学习如何进行内存分配,选用怎样的内存分配方式来适应不同的算法需求。比如,函数局部变量尽量使用栈,全局变量、结构体成员使用堆分配等。程序员不得不花费很长的时间在不同的项目中学习、记忆这些概念并加以实践和使用。Go语言将这个过程整合到了编译器中,命名为“变量逃逸分析”。通过编译器分析代码的特征和代码的生命周期,决定应该使用堆还是栈来进行内存分配。
1) 逃逸分析
通过下面的代码来展现Go语言如何使用命令行来分析变量逃逸,代码如下:
var c int
c = b
return c
}
func void() {
}
func main() {
var a int
void()
fmt.Println(a, dummy(0))
}
第 29 行,打印 a 和 dummy(0) 的返回值,测试函数返回值没有变量接收时的分析情况。 接着使用如下命令行运行上面的代码:
go run -gcflags “-m -l” main.go
使用 go run 运行程序时,-gcflags 参数是编译参数。其中 -m 表示进行内存分配分析,-l 表示避免程序内联,也就是避免进行程序优化。 上面例子中变量 c 是整型,其值通过 dummy() 的返回值“逃出”了 dummy() 函数。变量 c 的值被复制并作为 dummy() 函数的返回值返回,即使变量 c 在 dummy() 函数中分配的内存被释放,也不会影响 main() 中使用 dummy() 返回的值。变量 c 使用栈分配不会影响结果。