详解Go语言中的逃逸分析

 更新时间:2023年09月15日 10:42:30   作者:similar  
逃逸分析是编译器用于决定将变量分配到栈上还是堆上的一种行为,下面小编就来为大家详细讲讲go语言中是如何进行逃逸分析的,需要的小伙伴可以参考下

什么是逃逸

一句话,逃逸分析是编译器用于决定将变量分配到栈上还是堆上的一种行为

众所周知,函数的运行都在操作系统内存空间中的栈空间内。我们在栈上声明临时变量,分配内存,函数运行完毕后,回收内存。每个函数的栈空间都是独立的,其他函数没有权限访问。但在某些情况下,我们需要在函数结束以后访问栈上面的某些数据,这就涉及到内存逃逸了。

如果变量从栈上逃逸,那么他会逃到哪儿去呢?他会跑到堆上。由于栈上的变量是在函数结束的时候自动进行回收,回收代价比较小;而堆空间分配内存,则首先需要找到一块大小合适的内存,之后通过GC回收才能释放。对于这种情况,频繁使用垃圾回收会占用比较大的开销,所以要尽量分配内存到栈上,减少GC的压力。

逃逸分析基本过程

Go语言的逃逸分析最基本的原则:如果一个函数返回一个对变量的引用,那么他就会发生逃逸。

在任何情况下,如果一个值被分配到了栈空间以外的地方,那么它一定是被分配到了堆上。简言之:编译器会分析代码的特征和生命周期,Go中的变量只有在编译器可以证明在函数返回后不会再被引用的情况下,才会被分配到栈上,否则会被分配到堆上。

不同于C++中的new,Go语言中的new关键字不一定会将内存分配到堆空间上,在Go语言中,没有关键字或者函数可以直接将变量分配到堆上,而是通过编译器来分析代码决定将变量分配到何处。

一句话:

编译器会根据变量是否被外部引用来决定是否逃逸。

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

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

常见逃逸情况

指针逃逸

我们知道传递指针可以减少底层值的拷贝,提高效率。但是如果拷贝的数据量小,指针传递会产生逃逸,可能会使用堆空间,增加GC负担,所以传递指针不一定是高效的。

比如:

package main
type Student struct {
	Name string
	Age int
}
func StudentRegister(name string, age int) *Student {
	s := new(Student) // 局部变量s逃逸到堆
	s.Name = name
	s.Age = age
	return s
}
func main() {
	StudentRegister("similar", 18)
}

虽然函数StudentRegister内部s为局部变量,但是由于返回了指针,其指向的内存地址不会是栈而是堆,这是典型的逃逸案例。

使用命令 go build -gcflags '-m -l' main.go,得到:

# command-line-arguments
./escape.go:8:22: leaking param: name
./escape.go:9:10: new(Student) escapes to heap # 表示该行内存发生了逃逸现象

栈空间不足

如果分配太大容量的slice在栈上,当栈空间不足存放当前对象或者无法判断当前切片长度时就会将对象分配到堆中。

package main
func MakeSlice() {
	s := make([]int, 10000, 10000)
	for index := range(s){
		s[index] = index
	}
}
func main() {
	MakeSlice()
}

同样使用命令go build -gcflags '-m -l' main.go:

# command-line-arguments
./escape_1.go:4:11: make([]int, 10000) escapes to heap

动态类型逃逸

很多函数的参数为interface类型,比如:

func Printf(format string, a ...interface{}) (n int, err error)
func Scanf(format string, a ...interface{}) (n int, err error)
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)

在编译时很难确定其参数的具体类型,也能产生逃逸。

变量大小不确定

在创建切片,初始化切片容量的时候,有时会传入一个变量指定其大小,由于变量的值不能被编译器确定,所以不能确定其占用空间大小,从而编译器可能会直接将变量分配到堆上。

package main
func MakeSlice() {
	length := 1
	a := make([]int, length, length)
	for i := 0; i < length; i++ {
		a[i] = i
	}
}
func main() {
	MakeSlice()
}

编译结果:

# command-line-arguments
./escape_1.go:5:11: make([]int, length, length) escapes to heap

常见的逃逸情况总结

指针逃逸:函数内部返回一个局部变量指针

分配大对象:导致栈空间不足,不得不分配到堆上

调用接口类型的方法,接口类型的方法调用是动态调度 - 实际使用的具体实现只能在运行时确定。

尽管能够符合分配到栈的场景,但是其大小不能在编译的时候确定,也会分配到堆上。

如何避免

Go中的接口类型的方法调用是动态调度,因此不能够在编译阶段确定,所有类型结构转换成接口的过程会涉及到内存逃逸的情况发生。如果对于性能要求比较高且访问频次比较高的函数调用,应该尽量避免使用接口类型。

由于切片一般都是使用在函数传递的场景下,而且切片在append的时候可能会涉及到重新分配内存,如果切片在编译期间的大小不能够确认或者大小超出栈的限制,多数情况下都会被分配到堆上。

总结

堆上分配内存比栈上分配内存,开销大很多。

变量分配在栈上需要能够在编译期确定他的作用域,否则会分配到堆上。

Go语言编译器会通过变量是否被外部引用类决定是否逃逸

通过go build -gcflags '-m'命令可以观察变量是否逃逸

不能盲目使用变量的指针作为函数参数,虽然会减少复制操作,但是当参数为变量自身的时候,复制是在栈上完成的操作,开销远比变量逃逸后动态地在堆上分配内存少得多。

到此这篇关于详解Go语言中的逃逸分析的文章就介绍到这了,更多相关Go逃逸分析内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 解决golang post文件时Content-Type出现的问题

    解决golang post文件时Content-Type出现的问题

    这篇文章主要介绍了解决golang post文件时Content-Type出现的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-05-05
  • Go语言中的Base64编码原理介绍以及使用

    Go语言中的Base64编码原理介绍以及使用

    Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,可用于在HTTP环境下传递较长的标识信息,下面这篇文章主要给大家介绍了关于Go语言中的Base64编码原理介绍以及使用的相关资料,需要的朋友可以参考下
    2022-01-01
  • go语言编程二维码生成及识别

    go语言编程二维码生成及识别

    这篇文章主要为大家介绍了go语言编程二维码的生成及识别示例演示,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-04-04
  • 使用Golang实现对网络数据包的捕获与分析

    使用Golang实现对网络数据包的捕获与分析

    在网络通信中,网络数据包是信息传递的基本单位,抓包是一种监控和分析网络流量的方法,用于获取网络数据包并对其进行分析,本文将介绍如何使用Golang实现抓包功能,包括网络数据包捕获和数据包分析,需要的朋友可以参考下
    2023-11-11
  • Golang Recover处理错误原理解析

    Golang Recover处理错误原理解析

    Golang 中的 recover 是一个鲜为人知但非常有趣和强大的功能,让我们看看它是如何工作的,以及在 Outreach.io 中如何利用它来处理 Kubernetes 中的错误
    2023-12-12
  • golang如何获取域名ip dns信息

    golang如何获取域名ip dns信息

    这篇文章主要介绍了golang如何获取域名ip dns信息问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-07-07
  • 浅析Go语言中的逃逸分析

    浅析Go语言中的逃逸分析

    逃逸分析算是go语言的特色之一,所以这篇文章小编就来和大家聊聊为什么不应该过度关注go语言的逃逸分析,感兴趣的小伙伴可以跟随小编一起了解一下
    2024-10-10
  • Go语言服务器开发之客户端向服务器发送数据并接收返回数据的方法

    Go语言服务器开发之客户端向服务器发送数据并接收返回数据的方法

    这篇文章主要介绍了Go语言服务器开发之客户端向服务器发送数据并接收返回数据的方法,实例分析了客户端的开发技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-02-02
  • Go自定义数据序列化的流程详解

    Go自定义数据序列化的流程详解

    在Go语言中,自定义数据的序列化是一个常见的需求,本文将深入探讨 Go 语言中自定义数据序列化的流程,包括关键概念、技巧和最佳实践,旨在帮助开发者更高效地进行数据序列化工作,需要的朋友可以参考下
    2024-06-06
  • Go语言break跳转语句怎么使用

    Go语言break跳转语句怎么使用

    这篇文章主要介绍了Go语言break跳转语句怎么使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-01-01

最新评论