Go内存逃逸(一文搞懂)

一、来由

       在 C 语言和 C++ 这类需要手动管理内存的编程语言中,将对象或者结构体分配到栈上或者堆上是由工程师自主决定的,这也为工程师的工作带来的挑战,如果工程师能够精准地为每一个变量分配合理的空间,那么整个程序的运行效率和内存使用效率一定是最高的,但是手动分配内存会导致如下的两个问题:        

1. 不需要分配到堆上的对象分配到了堆上 — 浪费内存空间;      

 2. 需要分配到堆上的对象分配到了栈上 — 悬挂指针、影响内存安全;          

       在编译器优化中,逃逸分析是用来决定指针动态作用域的方法,Go 语言的编译器使用逃逸分析决定哪些变量应该在栈上分配,哪些变量应该在堆上分配,提高存储性能 

二、定义

       编译器将原本应该分配到栈上的变量,分配到了堆上(延长了变量的生命周期,在编译阶段完成)

科普:

在Go语言中,Go程序会在两个地方为变量分配内存,一个是全局的堆空间,另一个是栈空间  

1.栈空间:          

        函数调用的参数,返回值以及小类型局部变量大都会被分配到栈上,这部分内存会由编译器进行管理,无需GC的标记  

2.堆空间:      

       全局变量,大对象,逃逸的变量会被分配到堆上,go运行时GC就会在后台将对应的内存进行标记从而能够在垃圾回收的时候将对应的内存回收,进而增加了开销

三、解决的问题

       1. 减少堆内存的分配

       2. 降低空间的消耗

       3. 降低GC处理次数,减轻负担

四、产生情况

1. 必然发生逃逸的四种情况:

       (1)在某个函数中new或字面量创建出的变量,将其指针作为函数返回值(指针逃逸)         (2)被已经逃逸的变量引用的指针,一定发生逃逸。  
       (3)被指针类型的slice、map和chan引用的指针一定发生逃逸  

       (4)闭包    

       (5)栈空间不足

  2. 可能发生逃逸的情况:

       (1) 将指针作为参数传给别的函数,在被传入函数的处理过程中,如果发生了上边的几种情况,则会逃逸,否则不会

五、对应代码案例

① 指针逃逸:

(1) 在test函数当中,a是一个局部变量,所以被分配在栈上,  

(2)紧接着,函数返回了a的地址,也就是一个指向局部变量的指针但由于a被分配在栈上,所以当 test 返回时,a的内存空间会被回收,而返回的指针将指向一个已经被释放的内存地址,这会导致 指针悬挂 问题;  

(3)为了避免这种情况,go编译器会将a分配到堆上,确保返回的指针在函数返回后仍有效

 ② 被已经逃逸的变量引用的指针:

      ① 第一种情况:  会发生内存逃逸,因为存在被已经逃逸的变量引用的指针情况(演示时会给大家看Println的源码)

       ②第二种情况:  不会发生内存逃逸

③ 指针类型的slice、map和chan引用

b不会发生内存逃逸,d和f会发生内存逃逸,被指针类型的 slice 引用

④ 闭包

       Increase() 返回值是一个闭包函数,该闭包函数访问了外部变量 n,那变量 n 将会一直存在,直到 in 被销毁。很显然,变量 n 占用的内存不能随着函数 Increase() 的退出而回收,因此将会逃逸到堆上。

⑤栈空间不足

       generate8192() 创建了大小为 8192 的 int 型切片,恰好等于 64 KB(64位机器上,int 占 8 字节),不包含切片内部字段占用的内存大小,只要单个变量不超过64KB,就不会发生逃逸。不会发生逃逸    

        generate8193() 创建了大小为 8193 的 int 型切片,恰好大于 64 KB。会发生逃逸                          generate(n),切片大小不确定,调用时传入。会发生逃逸

         8192来源:在64位系统中,一个int类型通常占用8个字节,所以8192个整数的切片占用的内存大小位 8192*8 = 65536个字节,也就是64KB,刚好处于阈值范围之内。

六、影响

1. 在栈上分配和回收内存的开销很低,效率非常高,因为其消耗的仅仅是将数据拷贝到内存的时间

2. 而在堆上分配内存,一个很大的额外开销则是垃圾回收

3. 所以说如果频繁发生内训逃逸,会导致程序占用过多的内存资源:            

       (1)内存占用增加,内存逃逸会延长变量的生命周期并且将其分配到堆上,会导致程序占用的内存资源不断增加          

       (2)性能下降:相比于栈分配,堆分配需要更多的 CPU 和内存资源,因此会导致程序的运行速度变慢。        

       (3)程序不稳定:如果程序中存在大量的内存逃逸,可能会导致垃圾回收器频繁工作,从而影响程序的稳定性。

七、利用逃逸分析提高性能

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

       2. 一般情况下,对于需要修改原对象值,或占用内存比较大的结构体,选择传指针。对于只读的占用内存较小的结构体,直接传值能够获得更好的性能

八、有无替代品

                                         手动管理内存代替编译器自动管理    

       Go语言的自动管理内存方案目前为止相较于其他语言相对于便捷,对于提高性能有很大优势,如若我们不想利用此分配方案,我们可以自己手动创造一套管理内存的方案代替此方案。

九、sync.Pool

      栈内存回收是用完即消,堆内存回收是通过GC回收内存,这大大增加了内存的分配开销,频繁创建和 销毁短期对象,会导致大量内存分配操作,增加了  CPU 的压力;

      而Go语言中提供了 sync.Pool 大大提升了性能,减少内存频繁开销和回收的压力;       

     举例:   现在要分配一个a变量大小的内存,可以通过一个对象池(Pool结构体),在里面指定内存大小,用完之后,清空该对象池,方便下次复用。(作者下去研究一下,下次给大家分享)

Go语言中,内存逃逸指的是当一个对象的指针被多个方法或线程引用时,这个指针会逃逸到堆上。内存逃逸的位置由编译器决定,而不像C或C++可以使用malloc或new在堆上分配内存。根据内存分配的基本原则,当函数外部对指针没有引用时,优先分配在栈上;当函数外部对指针存在引用时,优先分配在堆上;当函数内部分配一个较大对象时,优先分配在堆上。\[1\] 在Go语言中,内存逃逸分析可以通过一些规则来判断。例如,当使用等号=赋值时,如果Data和Field都是引用类型的数据,则会导致Value逃逸。另外,一些特定的数据类型也会导致逃逸,比如\[\]interface{}、map\[string\]interface{}、map\[interface{}\]interface{}、map\[string\]\[\]string、map\[string\]*int、\[\]*int、func(*int)、func(\[\]string)、chan \[\]string等。具体的规则可以参考引用\[2\]中的示例。\[2\] 此外,栈空间不足也可能导致内存逃逸。当在函数中创建一个较大的切片或数组,并且栈空间不足以容纳它们时,这些切片或数组会逃逸到堆上。例如,在一个函数中创建一个长度为10000的切片,如果栈空间不足,这个切片就会逃逸到堆上。\[3\] 总结来说,内存逃逸是指当一个对象的指针被多个方法或线程引用时,这个指针会逃逸到堆上。在Go语言中,内存逃逸的位置由编译器决定,可以通过一些规则和栈空间的判断来进行分析。 #### 引用[.reference_title] - *1* [golang内存逃逸分析](https://blog.csdn.net/qq_42170897/article/details/127770234)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [golang内存逃逸](https://blog.csdn.net/wanghao3616/article/details/107284523)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [golang——内存逃逸机制](https://blog.csdn.net/weixin_45627369/article/details/127163797)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值