GoLang逃逸分析讲解
上后左爱 人气:0概念
当一个对象的指针在被多个方法或者线程引用,称为逃逸分析, 逃逸分析决定一个变量分配在堆上还是栈上, 当然是否发生逃逸是由编译器决定的
分配栈和堆上变量的问题
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 逃逸
将指针作为入参传给别的函数,这里看指针在被传入函数的处理过程,如果发生上边三种情况会逃逸,否则不会
加载全部内容