「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战」。
逃逸分析
逃逸分析是编译器确定由程序创造出的变量的位置的过程。编译器通过静态代码分析,决定一个变量应该放在函数对应的栈帧上或者“逃逸”到堆上。在Go中没有直接的关键词或者函数可以直接明确变量分配在栈或者堆上。
例如,函数中申请一个对象
- 如果分配在栈中,函数执行结束时自动回收
- 如果分配在堆中,函数结束后某个时间点进行垃圾回收
在栈上分配或者回收内存的开销很低,在堆上分配内存,很大的额外开销是垃圾回收。
go build -gcflags=-m
编译时设置-gcflags=-m,可以查看逃逸情况。
堆、栈、逃逸机制
堆(heaps)
堆和栈一样不会自我清理。所以使用这种内存会有大的损耗。首先是GC,会涉及到一片区域的清理。当GC运行时。将会使用25%的cpu容量。还会产生数微妙的stw延时。有GC的好处是不用担心如何管理堆内存。堆上的值构成了go中的内存分布。这些分配给了GC极大压力,因为每个堆上的值不再被指针引用时,就需要被移除。越多的值被检查和移除,每次Gc启动时就需要做更多的工作。因此,节奏算法(pacing algorithm)一直在平衡堆的大小以及自己的运行的速度。
共享栈(sharing stacks)
在go中,没有goroutine 被允许拥有指向其他goroutine栈上内存的指针。goroutine的栈内存随着栈的增长或收缩时会被新的一块内存所替换。goroutine被调度时,绑定操作系统内核线程执行,栈空间不会超过操作系统线程的栈空间。
逃逸机制(Escape Mechanics)
任何时候,如果一个值被共享在函数的栈帧范围之外,该值就会被分配在堆上。逃逸分析算法的职责就是寻找到这样的情况并且保持一定级别的完整性(integrity 确保获取任何值是精确、一致和高效的)。
逃逸情况
指针逃逸
函数中创造对象,返回对象的指针。随着函数执行结束,由于指针存在,对象占用的内存不会回收,只能分配在堆上。
动态类型逃逸
如果函数参数为 interface{},编译器很难确定函数参数的具体类型,发生逃逸。
栈空间不足
超过一定大小的局部变量将逃逸到堆上。
总结
go逃逸分析涉及到堆、栈、静态代码分析、垃圾回收机制。编译时期决定一个变量是否“逃逸”到堆上。掌握逃逸分析的多种情况,可以进一步调整和优化go代码。