Golang内存逃逸分析

概述

在Go里面定义了一个变量,到底是分配在堆上还是栈上,Go官方文档告诉我们,不需要管,他们会分析,其实这个分析就是逃逸分析

通俗来讲,当一个对象的指针被多个方法或线程引用时,我们称这个指针发生了逃逸。

Golang内存分配的基本原则

如果函数外部没有引用,则优先放到栈中;

如果函数外部存在引用,则必定放到堆中;

如果一个变量过大,则有可能分配在堆上

逃逸分析

变量在栈或是堆上分配内存,是由编译器决定的

在 build 的时候,通过添加 -gcflags "-m" 编译参数就可以查看编译过程中的逃逸分析

为什么要进行逃逸分析

提到逃逸分析,不得不提的是堆分配和栈分配的差异。

堆适合不可预知的大小的内存分配。但是为此付出的代价是分配速度较慢,而且会形成内存碎片。堆分配带来的另一大问题是gc,gc会消费cpu的时间。减少内存逃逸则直接降低了gc处理的时间。

栈内存分配则会非常快,栈分配内存只需要两个CPU指令:“PUSH”和“RELEASE”分配和释放;而堆分配内存首先需要去找到一块大小合适的内存块。之后要通过垃圾回收才能释放。

案例

指针逃逸

代码

  1 package main
  2 
  3 func main() {
  4     test()
  5 }
  6 
  7 func test() *int {
  8     i := 1
  9     return &i
10 }

编译

% go build -gcflags "-m"
./main.go:7:6: can inline test
./main.go:3:6: can inline main
./main.go:4:9: inlining call to test
./main.go:8:5: moved to heap: i

解释

虽然itest函数声明的一个局部变量,但golang支持变量i的作用域扩展到函数外部,其方式就是通过内存逃逸,将该变量在heap上分配。

栈空间不足导致的逃逸

代码

func main() {
    stack()
}

func stack() {
    s := make([]int, 100000, 100000)
    s[0] = 1
}

编译

% go build -gcflags "-m"
./main.go:7:6: can inline stack
./main.go:3:6: can inline main
./main.go:4:10: inlining call to stack
./main.go:4:10: make([]int, 100000, 100000) escapes to heap
./main.go:8:14: make([]int, 100000, 100000) escapes to heap

解释

栈的大小是有限制的,如果变量过大,则直接分配到堆上。

如果将上述case中slice的cap设置为10,使用同样的方法分析,可以不会发生内存逃逸。

使用new的内存分配方式同make是一致的。

动态类型逃逸

代码

  3 func main() {
  4     dynamic()
  5 }
  6 
  7 func dynamic() interface{} {
  8     i := 0
  9     return i
 10 }

编译

% go build -gcflags "-m"
./main.go:7:6: can inline dynamic
./main.go:3:6: can inline main
./main.go:4:12: inlining call to dynamic
./main.go:4:12: i does not escape
./main.go:9:5: i escapes to heap

解释

我们可以看到dynamic函数被inline处理掉了,所以变量i声明的地方由代码中的第8行被标识到第4行。因为dynamic函数的返回值interface{}是一个动态类型,在编译期不同确定动态类型的实际使用空间,因此i被逃逸分配到heap上了。

闭包逃逸

代码

  3 func main() {
  4     f := fibonacci()
  5     for i := 0; i < 10; i++ {
  6         f()
  7     }
  8 }
  9 
 10 func fibonacci() func() int {
 11     a, b := 0, 1
 12     return func() int {
 13         a, b = b, a+b
 14         return a
 15     }
 16 }

编译

% go build -gcflags "-m"
./main.go:12:12: can inline fibonacci.func1
./main.go:11:5: moved to heap: a
./main.go:11:8: moved to heap: b
./main.go:12:12: func literal escapes to heap

解释

不仅变量a、b发生了逃逸,返回值函数对象也被分配到堆上了

参考文献

https://www.jianshu.com/p/dcd87431a8af

https://segmentfault.com/a/1190000020086727?utm_source=sf-similar-article

https://zhuanlan.zhihu.com/p/113643434

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值