go-基于逃逸分析来提升性能程序
为什么要学习逃逸分析:
为了提高程序的性能,通过逃逸分析我们能知道指标是分配到堆上还是栈上,如何是 分配到栈上,内存的分配和释放都是由编译器进行管理的,分配和释放的速度都非常的快;如果分配到堆上,堆不像栈那样可以自动清理,它会引起崩溃进而进行垃圾回收,而垃圾回收又会占用比较大的系统开销。
什么是逃逸分析
逃逸分析简单来说就是分析在程序的哪些位置可以访问到该指针
简单的来说,它是在对变量放在堆上或者栈上进行分析,该分析在编译阶段完成。如果一个变量超过了函数调用的生命周期,那么这个变量就在函数外部存在引用了,编译器会把这个变量分配到堆上,接下来我们就说这个变量发生逃逸了。
如何确定是否逃逸
go
go run -gcflags '-m' main.go
可能出现逃逸的场景
go
pakcage main
type Student struct {
Name interface{}
}
func main() {
stu := new(Student)
stu.Name = "tom"
}
- 函数
main
可以被内联(can inline main)。 - 在第45行,
new(Student)
函数调用没有逃逸到堆上分配(does not escape)。 - 在第46行,字符串
"tom"
逃逸到了堆上分配(escapes to heap)。
interface赋值会发生逃逸,优化方案只需要将类型设置为固定类型,例如:string
02
go
package main
type Student struct {
Name string
}
func GetStudent() *Student {
stu := new(Student)
stu.Name = "tom"
return stu
}
func main() {
GetStudent()
}
- 函数
GetStudent
可以被内联(can inline GetStudent)。 - 函数
main
可以被内联(can inline main)。 - 在第55行,
new(Student)
函数调用逃逸到堆上分配(escapes to heap)。 - 在第61行,
new(Student)
函数调用没有逃逸到堆上分配(does not escape)。 - 在第61行,调用
GetStudent
函数被内联(inlining call to GetStudent)。
返回指针类型,会发生逃逸
函数传递指针和传值,传递指针可以减少底层值的拷贝,可以提高效率,但是如果拷贝的数据量小,由于指针传递会产生逃逸,可能会使用堆,也可能会增加GC的负担,所以交付负载不一定是高效的。
不要盲目使用指针作为参数,虽然减少了复制,但指针逃逸的头部可能更大
go
package main
func main() {
nums := make([]int, 10000, 10000)
for i := range nums {
nums[i] = i
}
}
- 函数
main
可以被内联(can inline main)。 - 在第66行,使用
make
函数创建的切片逃逸到了堆上分配(escapes to heap)
栈空间不足,发生逃逸,设置合理容量
总结
- 逃逸分析是编译器在静态编译时完成的
- 逃逸分析后可以确定哪些变量可以在分配栈上,栈的性能好