Go语言内存逃逸的示例分析

发布时间:2021-06-17 09:24:35 作者:小新
来源:亿速云 阅读:167

这篇文章主要为大家展示了“Go语言内存逃逸的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Go语言内存逃逸的示例分析”这篇文章吧。

我们在高中学过一些天体物理的知识,比如常见的三个宇宙速度:

了解了航天器的逃逸行为,我们今天来点特别的:内存逃逸。

通过本文你将了解到以下内容:

Part1C/C++的内存布局和堆栈

这应该是一道出现频率极高的面试题。

C/C++作为静态强类型语言,编译成二进制文件后,运行时整个程序的内存空间分为:

内核空间主要存放进程运行时的一些控制信息,用户空间则是存放程序本身的一些信息,我们来看下用户空间的布局:

Go语言内存逃逸的示例分析

堆和栈的主要特点:

Go语言与C语言渊源极深,C语言面临的问题,Go同样会面对,比如:变量的内存分配问题。

Part2Go的内存逃逸和逃逸分析

如果写过C/C++都会知道,在函数内部声明局部变量,然后返回其指针,如果外部调用则会报错:

#include <iostream> using namespace std;  int* getValue() {  int val = 10086;  return &val; }  int main() {    cout<<*getValue()<<endl;    return 0; }

编译上述代码:main.cpp: In function &lsquo;int* getValue()&rsquo;: main.cpp:7:9: warning:  address of local variable &lsquo;val&rsquo; returned [-Wreturn-local-addr]

用同样的思想,写一个go版本的代码:

package main  import (  "fmt" )  func main() {     str := GetString()     fmt.Println(*str) }  func GetString() *string {     var s string     s = "hello world"     return &s }

代码却可以正常运行,我们本意是在栈上分配一个变量,用完就销毁,但是外部却调用了,甚至可以正常进行,表现和C++完全不同。

其实,这就是Go的内存逃逸现象,Go模糊了栈内存和堆内存的界限,具体来说变量究竟分配到哪里,是由编译器来决定的。

1逃逸分析escape analysis

所谓逃逸分析就是在编译阶段由编译器根据变量的类型、外部使用情况等因素来判定是分配到堆还是栈,从而替代人工处理。

一般将局部变量和参数分配到栈上,但是并不绝对:

编译器不清楚局部变量是否会被外部使用时,就会倾向于分配到堆上。

Go编译器在确定函数返回后不会再被引用时才分配到栈上,其他情况下都是分配到堆上。

这样做虽然浪费堆空间,但是有效避免了悬挂指针的出现,并且由于GC的存在也不会出现内存泄漏,权衡之下也是一种合理的做法。

2哪些情况会出现内存逃逸

对于Go来说,在日常使用中有几种常见的做法会导致内存逃逸现象的出现:

指针逃逸

在上一个例子中我们使用一个int指针来说明内存逃逸的现象,接下来我们扩展一下变为结构体指针,并且使用gcflags来给编译器传特定参数来观察逃逸现象:

// test.go package main  import "fmt"  type Escape struct {  who string }  func CallInstance(caller string) (*Escape) {  instance := new(Escape)  instance.who = caller  return instance }  func main() {  outer := CallInstance("hello world")  fmt.Println(outer.who) }

执行:go build -gcflags=-m test.go 如下:

# command-line-arguments ./test.go:9:6: can inline CallInstance ./test.go:16:23: inlining call to CallInstance ./test.go:17:13: inlining call to fmt.Println ./test.go:9:19: leaking param: caller ./test.go:10:17: new(Escape) escapes to heap ./test.go:16:23: main new(Escape) does not escape ./test.go:17:19: outer.who escapes to heap ./test.go:17:13: main []interface {} literal does not escape ./test.go:17:13: io.Writer(os.Stdout) escapes to heap <autogenerated>:1: (*File).close .this does not escape

我们可以看到"escapes to heap",确实出现了内存逃逸,本该在栈上逃逸到堆上了。

栈空间不足逃逸

对于64bit的Linux系统而言栈的大小一般是8MB,Go中每个goroutine初始化栈大小是2KB,在goroutine的运行过程中栈的大小可能会变化,但也不会超过OS对线程栈大小的限制。

在网上找了个例子,用mac跑了一下:

package main  import "math/rand"  func generate8191() {  nums := make([]int, 8191) // < 64KB  for i := 0; i < 8191; i++ {   nums[i] = rand.Int()  } }  func generate8192() {  nums := make([]int, 8192) // = 64KB  for i := 0; i < 8192; i++ {   nums[i] = rand.Int()  } }  func generate(n int) {  nums := make([]int, n) // 不确定大小  for i := 0; i < n; i++ {   nums[i] = rand.Int()  } }  func main() {     generate8191()     generate8192()     generate(1) }
# command-line-arguments ./test_3.go:6:14: generate8191 make([]int, 8191) does not escape ./test_3.go:13:14: make([]int, 8192) escapes to heap ./test_3.go:20:14: make([]int, n) escapes to heap

可以看到在分配8191个大小时未发生逃逸,在分配8192时发生了逃逸,不定长度也发生了逃逸。

其他情况

在go中map、interface、slice、interface是非常常见的数据结构,也是非常容易触发内存逃逸的根源。

Part3内存逃逸小结

我们该如何评价内存逃逸呢?

以上是“Go语言内存逃逸的示例分析”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注亿速云行业资讯频道!

推荐阅读:
  1. GoLang逃逸分析的机制是什么
  2. Java中逃逸问题的示例分析

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

go语言

上一篇:Github五月份热门的JavaScript开源项目有哪些

下一篇:Visual Studio 2010开始页有什么用

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》