Go 逃逸分析与性能 1、变量与内存 通常每种编程语言都有自己的内存模型,每个变量,常量都存储在内存的某个物理位置上,并通过内存指针来访问。 我们都知道,程序运行时所使用的的内存分为两个区:堆和栈。那我们怎么知道变量是分配在堆还是栈上呢?Go 语言实现了垃圾回收机制,其内存是自动管理的,所以通常作为开发者并不需要关心内存分配在栈还堆上。但是站在性能的角度,在栈上分配内存和堆上分配内存,两者的性能却非常大。因为分配在栈上的内存,函数直接结束就能自动回收;而分配在堆上的内存,需要等待垃圾回收才能被回收释放。 在 Go 官网的FAQ上有个变量分配的问题如下: 如何知道变量是分配在堆上还是栈上? 从正确性的角度来看,你不需要知道。只要有对它的引用,Go 中的每个变量就存在,而且变量选择的存储位置与语言的语义无关。 存储位置确实对程序性能有影响。如果可能,Go 编译器将在函数的栈上分配该函数的本地变量。但是,如果函数返回后无法保证该变量不再被引用,那么编译器必须在垃圾回收的堆上分配该变量以避免悬空指针错误。此外,如果局部变量非常大,将其存储在堆上而不是栈上可能更有意义。 在当前的编译器中,如果一个变量的地址被占用,那么该变量就是在堆上分配的候选者。但是,基本的逃逸分析会识别某些情况,将函数返回后不再存活的变量分配在栈上。 由此我们可以发现,变量逃逸一般发生在以下几种情况: 函数返回地址 函数返回引用 函数返回值类型不确定,或者说不确定其大小 变量过大 变量大小不确定 那么,知道变量逃逸的原因后,我们就可以有意识地将变量控制在栈上,减少堆变量的分配,降低GC成本,提高程序性能。 2、逃逸分析 Go 语言内存分配是由编译器决定的,编译器会跨越函数和包的边界进行全局的分析,检查是否需要在堆上为一个变量分配内存,还是在栈本身的内存对其进行管理,这个过程称为逃逸分析(escape analysis)。 2.1 变量大小逃逸 举个例子,我们模拟一个变量大小不确定的情况: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 package main func main() { num := 10 s1 := make([]int, 0, num) for i := 0; i < num; i++ { s1 = append(s1, i) } s2 := make([]int, 0, 10) for i := 0; i < num; i++ { s2 = append(s2, i) } } 编译时,指定编译参数-gcflags="-m"可以查看逃逸分析,结果如下:……

阅读全文