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

Go内存逃逸

时间:2023-01-15 17:58:25浏览次数:61  
标签: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包中的大部分函数时, 均会发生内存逃逸. 相关isuse: 8618 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发送指针变量
  • 等等

总结

综上, 介绍了内存逃逸的概念及常见情况. 当发生逃逸的时候, 会增加GC的压力. 变量放在哪里简单来说就是:

  1. 若在函数外部使用了, 则必放在堆中
  2. 若在函数外部没有使用, 则优先放到栈中, 若栈中放不下, 则放到堆中

那么我们在函数返回结构体使经常碰到的疑问: 返回"值类型"还是"指针类型"??

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


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

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

相关文章

  • (转)golang 函数
    原文:https://www.cnblogs.com/duoke360/p/15663943.htmlgolang函数简介函数的go语言中的一级公民,我们把所有的功能单元都定义在函数中,可以重复使用。函数包含函数的名称......
  • redis默认内存设置及调整
    1、redis默认内存:如果不设置最大内存大小或者设置最大内存大小为0,在64位才做系统下不限制内存大小,在32操作系统下最多使用3GB内存;2、生产上内存设置:一般推荐redis设置内存......
  • .Net 使用 MongoDB
    1、安装nuget包MongoDB.Driver2、简单代码usingMongoDB.Bson;usingMongoDB.Driver;usingSystem.Buffers;usingSystem.Collections.Concurrent;usingSystem.Di......
  • django DRF
    博客目录web应用模式api接口接口测试工具postmanrestful规范drf快速使用CBV源码分析drf之APIView分析drf之Request对象分析......
  • (11)go-micro微服务雪花算法
    目录一雪花算法介绍二雪花算法优缺点三雪花算法实现四最后一雪花算法介绍雪花算法是推特开源的分布式ID生成算法,用于在不同的机器上生成唯一的ID的算法。该算法生......
  • golang实现的一个小游戏–猜数字
    随机生成一个数字,输入一个数字看是否匹对,匹配则结速,反之提示是大了还是小了,代码如下:packagemainimport("bufio""fmt""math/rand""os""strconv""time")var......
  • 关于Flex Cairngorm中command层与View层的通信及耦合度高的问题的解决
    在Cairngorm框架中,view层发送自定义事件,command层再根据事件做相应的处理,View层中的数据通过绑定ModelLocator中的属性实现自动更新。现有的问题是,有时除了更新数据意外,用户......
  • Java 线程内存模型
    1.前言本节内容是从操作系统的层面谈并发,本节课程我们需要掌握如下内容:了解Java的内存模型定义,是Java并发编程基本原理的基础知识;从概念上了解线程的私有内存空间和主......
  • 编程:C语言内存的堆栈模型
    内存:C语言内存的堆栈模型    一、C语言内存的堆栈模型 1、栈:栈底是高地址,栈顶是低地址。栈空间的地址生长方向:从高地址到低地址。 2、堆:堆底是低地......
  • 通过Google Cloud Storage(GCS)管理Terraform的状态State
    管理Terraform状态文件的最佳方式是通过云端的统一的存储,如谷歌云就用GCS。首先要创建一个Bucket:$gsutilmb-ppkslow-lus-west1-bongs://pkslow-terraformCreat......