美文网首页
Golang逃逸分析

Golang逃逸分析

作者: pyihe | 来源:发表于2020-11-17 23:08 被阅读0次

前言

本文翻译自 Alysha Gardner 的一篇博文Golang escape analysis

由于原博客创作时间较早,文中的一些编译显示结果可能存在出入,请参照最新的Go版本编译结果。

正文

垃圾回收是Go语言的一项很方便的功能-自动管理内存让代码变得更干净并且内存泄漏更少。然而,GC同样增加了开销,因为程序需要阶段性地停止并且回收没用的对象。Go编译器足够聪明地自动决定一个变量是否应该分配到需要垃圾回收的堆上,或者是否能够分配到声明该变量的函数的栈结构中。栈变量不像堆变量,栈变量不会带来任何的GC开销,因为它们和剩余的栈数据一起在函数返回时被销毁。

Go的逃逸分析比HotSpot JVM的更简单。最基础的规则是如果一个变量的引用被声明它的函数返回了,那么它就"逃逸"了-它可以在函数返回后被引用,所以它必须分配在堆中。这是复杂的, 通过下面几点体现:

  • 函数调用其他函数
  • 引用被赋值给了结构体成员
  • 切片和映射
  • 使用指向变量的指针的cgo

为了演示逃逸分析,在编译期间,Go构建了一副函数调用的图用于追踪输入参数和返回值的流程。一个函数可能引用它的一个参数,但是如果该引用没有被返回,那么该变量不会逃逸。一个函数同样可能返回一个引用,但是在声明该变量的函数返回前,该引用可能被另一个栈中的函数引用或者没有被返回。为了阐述几个例子,我们使用-gcflags '-m'参数来运行编译器,该参数将会打印冗长的逃逸分析信息:

package main

type S struct{}

func main()  {
    var x S
    _ = identity(x)
}

func identity(x S) S {
    return x
}

你将会使用go run -gcflags '-m -l'编译这个程序--l标识阻止函数identity的内联。该程序将什么也不输出。Go使用值传递语义,所以main中的变量x将总是被拷贝到identity所处的栈中。通常不带引用的代码总是使用栈内存分配,不会有逃逸分析。让我们尝试更难的事情:

package main

type S struct{}

func main()  {
    var x S
    y := &x
    _ = *identity(y)
}

func identity(z *S) *S {
    return z
}

输出为:

./escape.go:11: leaking param: z to result ~r1
./escape.go:7: main &x does not escape

第一行显示了变量的流向: 输入的变量作为输出被返回了。但是identity()没有引用z, 所以变量没有逃逸。在main返回的的经过中,没有x的引用幸存,所以x可以作为main栈结构的的一部分被分配。

第三个试验:

package main

type S struct{}

func main()  {
    var x S 
    _ = *ref(x)
}

func ref(z S) *S {
    return &z
}

输出:

./escape.go:10: moved to heap: z
./escape.go:11: &z escapes to heap

现在有逃逸发生,记住Go是值传递,所以z是来自main中的变量x的一份拷贝,ref返回z的一份引用,所以z不能成为ref栈的一部分-那么当ref返回时该引用指向哪里呢?取而代之,它逃逸到了堆上,即使main在没有重复引用它前立即抛出该引用,Go的逃逸分析也没有熟练到能够识别出这种情况,它仅仅只是查看输入流和返回的变量,在这种情况下值得注意的是ref将会被编译器内联如果我们不停止它。

如果一个引用被赋值给了一个结构体成员会怎样呢?

package main

type S struct{
    M *int
}

func main()  {
    var i int
    refStruct(i)
}

func refStruct(y int) (z S) {
    z.M = &y
    return z
}

输出:

./escape.go:12: moved to heap: y
./escape.go:13: &y escapes to heap

在这种情况下Go仍然可以追踪引用的流程,即使该引用是一个结构体的成员。因为refStruct产生了引用并且返回了它,y必须逃逸,与这种情形比较:

package main

type S struct{
    M *int
}

func main()  {
    var i int 
    refStruct(&i)
}

func refStruct(y *int) (z S) {
    z.M = y
    return z
}

输出:

./escape.go:12: leaking param: y to result z
./escape.go:9: main &i does not escape

因为main发生了引用并且将其传递给了refStruct,该引用不会比声明它的栈存活得更长。这个和先前的程序有略微不同的语义,但是如果第二个程序是足够完整的话,它将会更高效:在第一个例子中i必须分配在main对应的栈中, 然后重新分配在堆中并且作为参数被拷贝到refStruct中。在第二个例子中,i只被分配一次,并且引用被传递。

一个稍微更隐秘一点的例子:

package main

type S struct{
    M *int
}

func main()  {
    var x S
    var i int
    ref(&i, &x)
}

func ref(y *int, z *S)  {
    z.M = y
}

输出:

./escape.go:13: leaking param: y
./escape.go:13: ref z does not escape
./escape.go:9: moved to heap: i
./escape.go:10: &i escapes to heap
./escape.go:10: main &x does not escape

这里的问题是y被赋值给了一个作为输入的结构体。Go不能追踪这种关系-(Go逃逸分析中输入只被允许流向输出)-所以逃逸分析失败并且变量必须被分配在堆中。由于Go逃逸分析的局限性,有很多文档记载的,非常规的情形(Go 1.5中)中变量必须被分配在堆上——参考链接

最后,关于映射(map)和切片如何呢?记住切片和映射实际上是带有指向分配在堆上的内存的指针的Go结构体:切片结构体在reflect包中的SliceHeader, 映射的结构体要稍微难找一点,但是它在hmap, 如果这些结构体没有逃逸那么它们将被分配在栈空间,但是底层数组或者hash桶中的数据每次都会被分配在堆中,避免这种情况的唯一办法是分配一个固定大小的数组(比如[10000]int)。

如果你已经分析了你的程序的堆使用情况并且需要减少GC时间,将频繁分配的变量从堆中移出可能会好一些。这里同样有一个吸引人的主题: 了解更多关于HotSpot JVM如何处理逃逸分析,查阅这片文章, 这篇文章讲解栈分配,并且还涉及辨别何时同步可以被省略。

相关文章

  • (十三)golang 逃逸分析

    Golang逃逸分析 介绍逃逸分析的概念,go怎么开启逃逸分析的log。以下资料来自互联网,有错误之处,请一定告之...

  • golang逃逸分析

    带GC语言给我们程序的编写带来了极大的便利,但是与此同时屏蔽了很多底层的细节,比如一个对象是在栈上分配还是在堆上分...

  • Golang逃逸分析

    前言 本文翻译自 Alysha Gardner[https://github.com/actgardner] 的一...

  • Golang 逃逸分析

    什么是逃逸分析 In compiler optimization, escape analysis is a me...

  • golang 逃逸分析_v1.0.0

    逃逸分析是golang编译器分析一个对象到底应该放到堆内存上,还是栈内存上逃逸是指在某个方法之内创建的对象,除了在...

  • GoLang-逃逸分析

    参考资源一参考资源二参考资源三 对于手动管理内存的语言,比如 C/C++,调用著名的malloc和new函数可以在...

  • Golang逃逸分析浅谈

    众所周知,Golang是一门自带GC的编程语言。这意味着内存的分配和管理绝大多数情况下不需要开发者去过多干涉。 在...

  • golang内存逃逸分析

    问题 开局就是一把问号,带着问题进行学习。请问 main 调用 GetUserInfo 后返回的 &User{.....

  • golang 垃圾回收(二)屏障技术 2020/6/3 10:4

    什么是屏障? golang 涉及到的三个写屏障 原理分析示例分析代码先看逃逸分析写屏障真实的样子 什么是屏障? 承...

  • Golang内存分配逃逸分析

    参考博客 https://www.jianshu.com/p/b85696ae6e71[https://www.j...

网友评论

      本文标题:Golang逃逸分析

      本文链接:https://www.haomeiwen.com/subject/nqfqiktx.html