通过Goalng内存逃逸分析讨论GC的压力

aside section ._1OhGeD · · 894 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

无GC语言是怎么运作的

一般来说,类似C/C++语言通过 malloc等方法分配的内存是在heap上的,但在Golang中却不是这样的,即便使用 new,也不一定分配在heap上,这也是我们今天要关注的问题。

那么为什么Go会这样呢?

其实也很简单,因为Go是有runtime的,实际分配在heap还是stack是由runtime决定的。看到这里可能会很奇怪,为什么有runtime,有GC我们还要关心分配在哪里呢?一切交给runtimeGC去管理就好了呀!

是的,确实是这样,但是有效的使用堆内存,避免因系统GC造成的STW带来的性能损失对于某些系统来说也是比较重要的。因此,接下来将说明Go的runtime如何决定变量分配在哪里的。

什么是逃逸分析?

在进入下面的内容前,先来解释下什么是逃逸分析。

其实很简单

逃逸分析就是由编译器确定内存在heap还是在stack,而不是程序员决定。

更进一步

如果在函数中申请一个新的对象

  • 如果分配 在栈中,则函数执行结束可自动将内存回收;
  • 如果分配在堆中,则函数执行结束可交给GC(垃圾回收)处理;

那怎么确定发生了逃逸呢?

如果本该分配在stack上的内存分配到了heap,则发生了逃逸。更准确的来说,如果一个变量在函数栈帧范围之外共享,它都会在堆上被重新分配,也就是发生了逃逸

逃逸场景

要有效的使用堆内存,就需要了解什么情况下发生逃逸。

场景一:指针逃逸

因为Go有指针机制,因此我们可以在函数结束时返回一个指针

// main.go
package main

func main() {
    _ = f(10, 20)
}

func f(x, y int) *int {
    n := new(int)
    *n = x * y
    return n
}

这段代码发生了逃逸,我们用 gcflags "-m -l" 来查看,其中 -l是阻止内联优化

.\main.go:8:10: new(int) escapes to heap

编译时显示第8行代码分配在heap上,也就是 n := new(int) 这段代码

场景二:栈空间不足逃逸

当我们分配的变量内存超过stack空间时,也会发生逃逸,如下代码

// main.go
package main

func main() {
    _ = make([]int, 1000, 1000)
}

编译时提示

.\main.go:4:10: main make([]int, 1000, 1000) does not escape

没有发生逃逸,也就意味着没有超过stack空间,我们加大内存分配

// main.go
package main

func main() {
    _ = make([]int, 1000, 10000)
}

将数量调整至 10000,再进行编译提示

.\main.go:4:10: make([]int, 1000, 10000) escapes to heap

发生了逃逸

场景三:动态分配逃逸

比如下面这段代码

package main

func main() {
    l := 10
    _ = make([]int, l)
}

编译时提示

.\main.go:5:10: make([]int, l) escapes to heap

发生逃逸的原因很简单,因为 l变量可能被更改,所以编译器认为应该分配到 heap

场景四:闭包引用对象逃逸

Go语言支持闭包机制的,因此也会发生逃逸,如下代码

// main.go
package main

func main() {
    _ = f()
}

func f() func() int {
    a, b := 0, 1
    return func() int {
        return a + b
    }
}

编译时提示

.\main.go:9:9: func literal escapes to heap
.\main.go:9:9: func literal escapes to heap

本来ab作为函数局部变量应该分配到stack中,但是由于f()函数返回了一个闭包函数,因此编译器认为ab应该分配到heap

场景五:其他函数栈使用了该内存

// main.go
package main

import "fmt"

func main() {
    a := new(int)
    fmt.Println(a)
}

编译时提示

.\main.go:7:13: a escapes to heap
.\main.go:6:10: new(int) escapes to heap
.\main.go:7:13: main ... argument does not escape

总结

逃逸分析在编译阶段完成,逃逸分析目的是决定内分配地址是栈还是堆,通过了解了逃逸分析后,我们可以了解内存究竟是在heap上分配还是在stack上分配,从而更有效的利用heap内存,从而减轻GC压力。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:aside section ._1OhGeD

查看原文:通过Goalng内存逃逸分析讨论GC的压力

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

894 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传