文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Go语言内存逃逸之谜

2024-12-03 04:26

关注

本文转载自微信公众号「后端技术指南针」,作者大白。转载本文请联系后端技术指南针公众号。

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

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

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

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

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

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

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

堆和栈的主要特点:

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

Part2Go的内存逃逸和逃逸分析

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

  1. #include  
  2. using namespace std; 
  3.  
  4. int* getValue() 
  5.  int val = 10086; 
  6.  return &val; 
  7.  
  8. int main() 
  9.    cout<<*getValue()<
  10.    return 0; 

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

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

  1. package main 
  2.  
  3. import ( 
  4.  "fmt" 
  5.  
  6. func main() { 
  7.     str := GetString() 
  8.     fmt.Println(*str) 
  9.  
  10. func GetString() *string { 
  11.     var s string 
  12.     s = "hello world" 
  13.     return &s 

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

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

1逃逸分析escape analysis

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

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

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

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

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

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

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

指针逃逸

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

  1. // test.go 
  2. package main 
  3.  
  4. import "fmt" 
  5.  
  6. type Escape struct { 
  7.  who string 
  8.  
  9. func CallInstance(caller string) (*Escape) { 
  10.  instance := new(Escape
  11.  instance.who = caller 
  12.  return instance 
  13.  
  14. func main() { 
  15.  outer := CallInstance("hello world"
  16.  fmt.Println(outer.who) 

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

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

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

栈空间不足逃逸

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

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

  1. package main 
  2.  
  3. import "math/rand" 
  4.  
  5. func generate8191() { 
  6.  nums := make([]int, 8191) // < 64KB 
  7.  for i := 0; i < 8191; i++ { 
  8.   nums[i] = rand.Int() 
  9.  } 
  10.  
  11. func generate8192() { 
  12.  nums := make([]int, 8192) // = 64KB 
  13.  for i := 0; i < 8192; i++ { 
  14.   nums[i] = rand.Int() 
  15.  } 
  16.  
  17. func generate(n int) { 
  18.  nums := make([]int, n) // 不确定大小 
  19.  for i := 0; i < n; i++ { 
  20.   nums[i] = rand.Int() 
  21.  } 
  22.  
  23. func main() { 
  24.     generate8191() 
  25.     generate8192() 
  26.     generate(1) 
  1. # command-line-arguments 
  2. ./test_3.go:6:14: generate8191 make([]int, 8191) does not escape 
  3. ./test_3.go:13:14: make([]int, 8192) escapes to heap 
  4. ./test_3.go:20:14: make([]int, n) escapes to heap 

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

其他情况

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

Part3内存逃逸小结

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

 

来源:后端技术指南针内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯