首页 > 系统相关 >Go内存逃逸

Go内存逃逸

时间:2023-06-07 16:22:17浏览次数:60  
标签:int test 逃逸 内存 func Go

前言
很久以前就听过过内存逃逸这个词, 最近了解了一下, 才发现是个很简单的概念. 只要把前言部分看完, 就已经了解了. 来吧…

在介绍内存逃逸之前, 我们先用C语言来引出这个概念.

我们的进程在内存中有栈内存和堆内存的概念, 栈内存是函数执行的局部内存, 会随着函数的结束而全部释放, 而堆内存是需要手动申请和管理的一部分内存. 这个概念大家都比较熟悉了, 在此就不再赘述.

c语言版本
在C中, 如果我们在函数中想要返回一个整形数组, 怎么写呢? 比如这样?

#include "stdio.h"
int* test(){
    int a[2] = {1, 3};
    return a;
}
int main() {
    int* a= test();
    printf("address: %p, %d", a, a[1]);
}

如果你这样做了, 可能会发现读到的数组数据是正确的, 但在使用gcc编译的时候会报警, 提示返回的a变量是一个栈内存地址. 这是因为test执行结束后, 这部分内存未来就会被其他地方使用, 结果正确仅仅是因为内存中的内容还没有被修改.

那么正确的写法应该是什么呢? 比如这样:

#include "stdio.h"
#include "stdlib.h"
int* test(){
    int *a = (int*) malloc(2);
    a[0] = 1;
    a[1] = 2;
    return a;
}
int main() {
    int* a = test();
    printf("address: %p, %d", a, a[1]);
    free(a);
}

test函数中申请一段内存, 并将内存的指针返回. 申请的内存就保存在堆内存中. 但是, 这样一来, 就不能享受栈内存的自动释放了, 需要再使用后调用free释放内存, 以便后续使用.

Go版本

那么在Go中如果我们想在函数中返回一个数组, 怎么写呢?

package main
import "fmt"
func test() *[3]int {
    var a [3]int
    a = [3]int{1, 2, 3}
    return &a
}
func main() {
    a := test()
    fmt.Printf("address: %p, %d", a, a[1])
}

这段代码和上面C版本的功能相同, 都是返回了数组的地址. 那么问题来了, 为什么同样是局部变量, Go就可以在函数返回之后仍能读到呢?

原因很简单, Go的编译器在检测到数组指针会在函数外部使用时, 自行将其放到了堆内存中. 而这, 就是Go中所说的内存逃逸现象了. 是不是看过之后感觉只是一个很简单的道理换了个名词而已.

其实到这里, Go的内存逃逸已经介绍完了, 一句话介绍就是: 局部变量被放到了堆内存中.

逃逸情况
因为内存逃逸后会放到堆内存中, 需要依赖GC进行释放, 而栈内存会自动释放, 无需GC参与. 因此在开发中减少内存逃逸, 可以减轻GC压力.

既如此, 有没有办法在一个Go程序中检查哪里会发生内存逃逸呢? (逃逸是发生在编译期的呦). 就是build命令:

go build -gcflags '-m -l' main.go

-m: 打印逃逸分析内容. 最多的添加4个-m, 获取更详细的信息
all=-m: 若编译时不止一个文件, 对所有文件应用-m
-l: 禁用函数内联. 可以更准确的定位逃逸位置.
all=-l: 同理
好, 基于此, 我们简单介绍几种内存逃逸的情况, 更多的情况可自行摸索. (以下所有情况, 可自行通过build命令分析查看)

返回局部变量指针
比如前言中的情况, 再或者:

func test() *int {
    a := 5
    return &a
}

超出栈大小

若对象在栈中放不下了, 也会发生逃逸. 栈的大小可通过命令查看: ulimit -a | grep stack

func test() {
    // 当内存申请超出栈大小时, 逃逸
    _ = make([]int, 8192*1024/8)
    // 当使用变量进行初始化时, 因为无法预知变量的大小, 也会逃逸
  // 如果可以的话, 将 n 改为 const, 就可以避免内存逃逸
    n := 2
    _ = make([]int, n)
}

闭包

闭包也很好理解, 因为变量在函数返回之后仍需要访问, 因此需要逃逸到堆上.

func test() func() int {
    a := 0
    return func() int {
        return a
    }
}

fmt 包

当使用fmt包中的大部分函数时, 均会发生内存逃逸. 相关isuse8618 7218

func main() {
    // 没有发生内存逃逸
    _ = reflect.TypeOf("1")
    // string kind 等发发会发生内存逃逸
    _ = reflect.TypeOf("1").String()
    _ = reflect.TypeOf("1").Kind()
    // 会发生内存逃逸, 因为其内部调用了 reflect.TypeOf("223").String()
    // 调用链: Println->Fprintln->doPrintln->printArg->reflect.TypeOf(arg).String()
    fmt.Println("223")
}

具体原因未做分析, 感兴趣的可以查看其内部实现. 期待后续版本可以优化吧.

其他情况
切片扩容后栈空间不足
channel发送指针变量. stackoverflow
等等
总结
综上, 介绍了内存逃逸的概念及常见情况. 当发生逃逸的时候, 会增加GC的压力. 变量放在哪里简单来说就是:

若在函数外部使用了, 则必放在堆中
若在函数外部没有使用, 则优先放到栈中, 若栈中放不下, 则放到堆中
那么我们在函数返回结构体使经常碰到的疑问: 返回"值类型"还是"指针类型"??

如果返回"值类型"就不会发生逃逸, 但是会触发内存复制. 如果返回"指针类型"就无需内存复制, 但是会发生逃逸. 因此就需要在GC与内存复制之间进行平衡, 判断哪个开销比较大. 一般来说, 若变量占用内存较小, 传值更为合适. 若内存较大, 则传递指针更为合适. (不过, 一般的项目都没有到"需要考虑 GC"的情况吧???)

原文地址: https://hujingnb.com/archives/884

原文链接:https://blog.csdn.net/qq_31725391/article/details/128696183

标签:int,test,逃逸,内存,func,Go
From: https://www.cnblogs.com/nizuimeiabc1/p/17463730.html

相关文章

  • Linux分析进程占用内存最高和占用CPU最高 的 命令
    Linux分析进程占用内存最高和占用CPU最高这里只显示最高的前5个,如果想显示更多的话,可以自己修改:查看占用内存最高的5个进程psaux|sort-k4nr|head-n5查看占用cpu最高的5个进程psaux|sort-k3nr|head-n5......
  • Golang中如何控制goroutine的执行顺序?
    首先说明一下原理:前后协程之间通过通道去相互限制,后一个线程尝试去获取一个channel的值,当channel中没有值时,就会一直阻塞,而前一个协程则负责关闭channel,当前一个协程完成了这个操作,后一个协程才可以结束阻塞,继续执行。示例代码:packagemainimport( "fmt" "time")funcma......
  • unity内存优化总结
    前言  一般Unity项目的内存主要分为如下方面:    资源内存    mono内存    dll内存    lua内存资源内存的分析与优化合理的资源标准  资源标准因项目而异1.如何定制合理的资源标准    1)根据项目定位受众的目标设备的性能峰值(比如内存不要超过2G),......
  • 深度学习应用篇-计算机视觉-图像分类[2]:LeNet、AlexNet、VGG、GoogleNet、DarkNet模型
    深度学习应用篇-计算机视觉-图像分类[2]:LeNet、AlexNet、VGG、GoogleNet、DarkNet模型结构、实现、模型特点详细介绍1.LeNet(1998)LeNet是最早的卷积神经网络之一<sup>[1]</sup>,其被提出用于识别手写数字和机器印刷字符。1998年,YannLeCun第一次将LeNet卷积神经网络应用到图像分类......
  • 深度学习应用篇-计算机视觉-图像分类[2]:LeNet、AlexNet、VGG、GoogleNet、DarkNet模型
    深度学习应用篇-计算机视觉-图像分类[2]:LeNet、AlexNet、VGG、GoogleNet、DarkNet模型结构、实现、模型特点详细介绍1.LeNet(1998)LeNet是最早的卷积神经网络之一[1],其被提出用于识别手写数字和机器印刷字符。1998年,YannLeCun第一次将LeNet卷积神经网络应用到图像分类上,在手写数......
  • 传奇GOM引擎补丁安装教程图解,传奇pak补丁介绍
    gameofmir引擎的加密补丁格式是PAK格式,该补丁放入客户端的位置也和传统的补丁不一样,导致很多玩家补丁都打错了,最后游戏不显示和黑屏常规的补丁文件夹就这几个datamapwav自定义补丁文件夹名字如名字就是自定义正确的自定义补丁使用方法如下:先带大家认识一下自定义补丁目录,每个版本作......
  • 通过redis学网络(1)-用go基于epoll实现最简单网络通信框架
    本系列主要是为了对redis的网络模型进行学习,我会用golang实现一个reactor网络模型,并实现对redis协议的解析。系列源码已经上传githubhttps://github.com/HobbyBear/tinyredis/tree/chapter1redis的网络模型是基于epoll实现的,所以这一节让我们先基于epoll,实现一个最简单的服......
  • Wwise内存问题
    1)Wwise内存问题​2)安卓平台特效显示不一致的问题3)多个矩形小方块组成的地形接缝处有黑线问题这是第339篇UWA技术知识分享的推送,精选了UWA社区的热门话题,涵盖了UWA问答、社区帖子等技术知识点,助力大家更全面地掌握和学习。UWA社区主页:community.uwa4d.comUWAQQ群:465082844Wwis......
  • mongodb分片
    分片是MongoDB的扩展方式,通过分片能够增加更多的机器来用对不断增加的负载和数据,还不影响应用.1.分片简介分片是指将数据拆分,将其分散存在不同机器上的过程.有时也叫分区.将数据分散在不同的机器上,不需要功能强大的大型计算机就可以存储更多的数据,处理更大的负载.使用几乎所有......
  • Go Swagger安装及使用
    地址:https://github.com/swaggo/gin-swagger安装根据go版本使用命令1.70之前goget-ugithub.com/swaggo/swag/cmd/swag1.70之后goinstallgithub.com/swaggo/swag/cmd/swag@latest查看是否成功swag-vswagversionv1.8.12其他安装命令goget-ugithu......