40

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

 4 years ago
source link: https://www.jianshu.com/p/10e83dc6cd64?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
0.2092019.09.30 17:17:14字数 923阅读 1,099

无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中有多个,下面将详细介绍

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

场景一:指针逃逸

因为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空间时,也会发生逃逸,如下代码

package main

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

编译时提示

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

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

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语言支持闭包机制的,因此也会发生逃逸,如下代码

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

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

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

逃逸分析在编译阶段完成,逃逸分析目的是决定内存地址分配在stack上还是heap上。

通过了解逃逸分析,我们在编写Go代码时就可以知道内存究竟是在heap上分配还是在stack上分配,然后更精细的控制heap上的内存分配,减轻GC压力。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK