本文主要是介绍Go 语言逃逸分析:内存管理的关键,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 前言
- 1 逃逸分析是什么?
- 2 逃逸分析的基本思想是什么?
- 3 逃逸分析的分配原则是什么?
- 4 如何进行逃逸分析?
- 5 逃逸分析案例
- 5.1 变量在函数外存在引用
- 5.2 引用类型的逃逸
- 5.3 闭包捕获变量
- 5.4 变量占用内存较大
- 6 变量会逃逸到堆上的原因有哪些?
- 7 如何避免逃逸?
- 8 逃逸分析的作用有哪些?
- 9 学习交流
前言
在正式学习逃逸分析时,我们需要提前补充栈、堆、变量的声明周期的概念。
- 栈(stack):在GO语言中,栈是调用栈(call stack) 的简称。在GO程序运行时,每一个Goroutine单独维护一个自己的栈区,仅允许自己使用不能被其他Goroutine使用。一个栈通常包含许多栈帧(stack frame),它描述的是函数之间的调用关系。栈的内存是由编译器自动进行分配和释放的。栈区主要存储函数参数、局部变量、调用函数帧,它们随函数的创建而分配,随函数的退出而销毁。
- 堆(heap):与栈(stack)不同的是,堆区的内存是由编译器和工程师共同负责管理分配,交给Runtime GC来释放。在堆上分配内存时,必须找到一块足够大的内存来存放新的变量数据。在堆上释放内存时,垃圾回收器会扫描内存空间中不被使用的对象并释放其内存。在我们开发过程中,其实考虑内存管理,主要是考虑堆内存的管理。
变量的声明周期与变量作用域的关系
- 全局变量:它的生命周期与程序的生命周期一致
- 局部变量:它的生命周期是动态的,从变量创建开始,到变量不再使用结束。
- 形参和函数的返回值:它们都是属于局部变量,在函数被调用时创建,调用结束时被销毁。
1 逃逸分析是什么?
逃逸分析(Escape Analysis)是一种重要的编译时优化技术,决定将变量分配到 堆(heap)上 还是 栈(stack)上。
通过逃逸分析,编译器可以判断变量的生命周期和作用范围,从而选择最合适的内存分配方式,以提高程序的性能和减少内存开销。
2 逃逸分析的基本思想是什么?
[!warning]- 思考: 如何知道GO变量的生命周期是完全可知的?
- 判断变量是值类型还是引用类型,值类型是确定的完全可知的,引用类型是不可知的,不知道是否该变量被其他函数使用。
- 检查变量的生命周期是否是完全可知的,如果是,则在栈上分配内存。
- 如何检查不是完全可知的,也就是我们说的逃逸,必须在堆上分配内存。
3 逃逸分析的分配原则是什么?
[!warning]- 如何确定参数类型是不确定的?
- 变量的数据类型采用
interface{}
,编译期无法确定其具体的参数类型,所以分配到堆中。- 什么样的数据类型是确定的? 比如:声明了一个确定数据类型int的变量`var num int
- GO的逃逸分析是在编译期间完成的,编译期间无法确定的参数类型是放在堆中的。
- 变量在函数外存在引用,则必定放在堆中
- 变量占用内存较大,则优先放在堆中
- 变量在函数外部没有引用,则优先放在栈中。
4 如何进行逃逸分析?
逃逸分析我们可以通过命令查看结果,
-gcflags
选项用于向 Go 编译器传递编译标志。这些标志可以用来启用或禁用特定的编译器功能,包括逃逸分析
- 查看基本的逃逸分析和内联信息,适用于一般情况
go build -gcflags="-m" main.go
[!warning]+ 命令解释说明
-m
:表示输出有关内联(inlining) 和逃逸分析的信息
- 查看更详细的优化信息和逃逸分析结果,并禁用内联优化,适用于需要深入调试和分析的情况。
go build -gcflags '-m -m -l' main.go
[!warning]+ 命令解释说明
-m -m
:表示多次使用-m
标志,增加详细程度,会输出更多的优化信息,包括逃逸分析和内联优化的详细信息。-l
:表示禁用内联优化。
5 逃逸分析案例
5.1 变量在函数外存在引用
package mainimport "fmt"func createPointer() *int {var x intreturn &x // x 逃逸到堆上
}func main() {p := createPointer()fmt.Println(*p)
}
[!note]+ 代码解析说明
- 函数
createPointer
返回了局部变量x
的地址,这意味着x
在函数返回后仍然需要存在。- 因此,编译器将
x
分配到堆上,并在逃逸分析的输出中提示&x escapes to heap
。
5.2 引用类型的逃逸
- 例如:切片、映射、接口等引用类型的变量,如果它们的底层数据逃逸,则这些变量也会逃逸。
func createSlice() []int {s := make([]int, 10) return s // s 逃逸到堆上
}
5.3 闭包捕获变量
如果闭包捕获了外部变量,该变量会逃逸到堆上。
func createClosure() func() { var x int return func() { x++ // x 逃逸到堆上 }
}
5.4 变量占用内存较大
func createManySlice() []int { var s []int for i := 0; i < 1000; i++ { s = make([]int, 10) } return s // s 逃逸到堆上
}
6 变量会逃逸到堆上的原因有哪些?
- 函数返回值:如果返回一个局部变量的指针或引用,该变量会逃逸到堆上。
- 闭包捕获:如果闭包捕获了外部变量,该变量会逃逸到堆上。
- 长生命周期:如果变量的生命周期超出了其作用域,如通过指针或引用传递给其他函数或存储在全局变量中。
7 如何避免逃逸?
避免逃逸,也就是说减少不必要的堆分配。
- 避免返回局部变量的指针或引用
- 尽量减少闭包捕获的外部变量
- 使用值传递而不是指针传递
8 逃逸分析的作用有哪些?
- 提升内存分配效率:栈上分配比在堆上分配效率更高效,栈上的内存可以自动回收,而堆上的内存需要垃圾回收器管理。
- 减少垃圾回收开销:减少不必要的堆分配,可以降低垃圾回收的频率和开销。
- 提高程序性能:优化内存分配,提升程序运行效率。
9 学习交流
为了方便大家一起学习一起进步,我创建了一个学习交流的平台
感兴趣的朋友们可以加我微信:LH913582934,备注:CSDN。
这篇关于Go 语言逃逸分析:内存管理的关键的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!