GoLang逃逸分析讲解

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

GoLang逃逸分析讲解

上后左爱   2022-12-15 我要评论

概念

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

分配栈和堆上变量的问题

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 逃逸

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

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们