为什么需要了解逃逸分析?
因为我们想要提升程序性能,通过逃逸分析我们能够知道变量是分配到堆上还是栈上,如果分配到栈上,内存的分配和释放都是由编译器进行管理,分配和释放的速度非常快,如果分配到堆上,堆不像栈那样可以自动清理,它会引起频繁地进行垃圾回收(GC),而垃圾回收会占用比较大的系统开销。
什么是逃逸分析?
逃逸分析是一种确定指针动态范围的方法,可以分析在程序的哪些地方可以访问到指针。它涉及到指针分析和形状分析。 当一个变量(或对象)在子程序中被分配时,一个指向变量的指针可能逃逸到其它执行线程中,或者去调用子程序。如果使用尾递归优化(通常在函数编程语言中是需要的),对象也可能逃逸到被调用的子程序中。 如果一个子程序分配一个对象并返回一个该对象的指针,该对象可能在程序中的任何一个地方被访问到——这样指针就成功“逃逸”了。如果指针存储在全局变量或者其它数据结构中,它们也可能发生逃逸,这种情况是当前程序中的指针逃逸。 逃逸分析需要确定指针所有可以存储的地方,保证指针的生命周期只在当前进程或线程中。
逃逸分析的用处
最大的好处应该是减少gc的压力,不逃逸的对象分配在栈上,当函数返回时就回收了资源,不需要gc标记清除。
因为逃逸分析完后可以确定哪些变量可以分配在栈上,栈的分配比堆快,性能好。
同步消除,如果你定义的对象的方法上有同步锁,但在运行时,却只有一个线程在访问,此时逃逸分析后的机器码,会去掉同步锁运行。
go消除了堆和栈的区别
go在一定程度消除了堆和栈的区别,因为go在编译的时候进行逃逸分析,来决定一个对象放栈上还是放堆上,不逃逸的对象放栈上,可能逃逸的放堆上。
开启逃逸分析日志
编译参数加入 -gcflags ‘-m -l’
例如:
go run -gcflags '-m -l' main.go
场景示例一
package main
type Student struct {
Name interface{}
}
func main() {
stu := new(Student)
stu.Name = "tom"
}
分析结果:
go run -gcflags '-m -l' 01.go
# command-line-arguments
./01.go:8:12: new(Student) does not escape
./01.go:9:11: "tom" escapes to heap
interface{} 赋值,会发生逃逸,优化方案是将类型设置为固定类型,例如:string
package main
type Student struct {
Name string
}
func main() {
stu := new(Student)
stu.Name = "tom"
}
分析结果:
go run -gcflags '-m -l' 01.go
# command-line-arguments
./01.go:8:12: new(Student) does not escape
场景示例二
package main
func main() {
nums := make([]int, 10000, 10000)
for i := range nums {
nums[i] = i
}
}
分析结果:
go run -gcflags '-m -l' 03.go
# command-line-arguments
./03.go:4:14: make([]int, 10000, 10000) escapes to heap