GoLang逃逸分析讲解

概念

当一个对象的指针在被多个方法或者线程引用,称为逃逸分析, 逃逸分析决定一个变量分配在堆上还是栈上, 当然是否发生逃逸是由编译器决定的

分配栈和堆上变量的问题

1.局部变量在栈上(静态分配),函数执行完毕后,自动被栈回收,导致其他对此变量引用出现painc null 指针异常, 栈用户态实现goroutine 作为执行上下文

2.将变量 new 方式分配在堆上(动态分配),堆上有个特点,变量不会被删除,但是会造成内存异常

// 如下代码导致 程序崩溃, 调用栈获取危险的悬挂指针
int *foo ( void)   
{     
int t = 3;
return &t;
} 

1. 栈上分配内存好处: 一般栈内存 2-4 MB
a. 回收快: 减少GC压力,当函数返回回收资源。不需要标记清除
b. 分配快栈分配比堆快,不会有内存碎片
c. 并发快, 清除同步,如果定义对象上有同步锁,却只有一个线程访问,此时逃逸分析机器码 去掉同步锁

总结: 逃逸分析目标:尽可能的使用栈分配内存 go build -gcflags ‘-m -N -l’ 方式编译逃逸分析结果

逃逸分析准则

如果一个函数返回对变量的引用,那么他就发生逃逸

  • 函数外部没有引用,优先分配到栈中(指向栈对象指针不能存在堆中)-- 该指针指向无效值或错误的内存值
  • 函数外部存在引用,必定分配到堆中(指向栈对象指针不能在栈对象回收后存活-- 指向的内存不合法)

CCN_ProLang/CoreGo/GoreGo 下面有对应的文档参考

逃逸分析大致思路

1.最重要函数 escape.go

$GOROOT/src/cmd/compile/internal/gc/escape.go

1. 首先构建一个有向无环图加权图,顶点(语句和表达式分配的变量),边(代表变量之间的赋值关系)
2. 遍历该有向加权图,图中违反上面两个不变条件的赋值路径,算法还记录每个函数的参数到堆的数据流和其返回值的数据流
权重

// p =&q -1 // 最低值
// p =q 0
// p = *q // 解引用 1
// p = **q 2

示例: root =&L , L 节点的指针指向root, 因此 root有一条边,src 就是L,该权重就是 -1

3. 逃逸分析: 分析 分配内存地方与使用 是否发生逃逸
4. go build -gcflags = "-m -m -m -m -W -W -N -l"

1. 当函数中变量返回值, 它将不可能分配在栈上

2.在循环内被重新赋值的变量大部分场景分配在堆上

3.在闭包外声明的变量在闭包内赋值失效后,需要分配在堆上

是否发生逃逸,这一点使用编译器决定的。导致后果:1. GC频繁导致CPU压力大 2.导致性能下降很大

1. 一些逃逸案例:
2. 函数返回变量取地址 导致逃逸
func GetUserInfo(userInfo UserData) *UserData{
   // 编译器判断外部使用 发生逃逸 ,传入的实参对象 取地址类似复制一份
	return &userInfo
}
//修改 将入参修改成指针, 中间没有新结构体没有变化 没有发生逃逸
func GetUserInfo(userInfo *UserData) *UserData {
		return userInfo
}
案例二:不确定类型逃逸
func MyPrintLn(one interface{}) (n int, err error){
	var userInfo = new(User)
	userInfo.name = one // 泛型赋值逃逸 类型转换时候发生逃逸
	return
}
变量确定具体类型
示例三: 间接变量赋值 闭包
var {
     UserOne User // 值对象
     userTwo = new(User) // 引用对象
}
userOne.name = "one" // 不逃逸
userTwo.name = "two" // 逃逸
userOne.age = new(int) // 不逃逸
userTwo.age = new(int) // 逃逸 引用对象在进行引用对象 只能分配堆上
引用对象: 编译器先分析器userTwo 对象分配到堆上,成员变量name,age 引用类型,保证不出现在栈上 导致对象userTwo 被回收 所有 name,age 需要逃逸
优化建议: 不要将引用对象赋值给引用对象

总结

必然不会发生逃逸的情况:

1. 指针被没有发生逃逸的变量引用

2. 仅仅在函数被对变量进行取地址操作,没有将指针传出

一定逃逸

构造函数new/make 返回的指针变量一定逃逸

2. 被已经逃逸指针变量引用指针,一定发生逃逸

3.指针类型是slice,map,chan 引用指针一定发生逃逸

Maybe 逃逸

将指针作为入参传给别的函数,这里看指针在被传入函数的处理过程,如果发生上边三种情况会逃逸,否则不会

到此这篇关于GoLang逃逸分析讲解的文章就介绍到这了,更多相关Go逃逸内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

你可能感兴趣的:(GoLang逃逸分析讲解)