本篇是根据 GopherCon SG 2019 “Understanding Allocations” 演讲的学习笔记。

Understanding Allocations: the Stack and the Heap - GopherCon SG 2019 - YouTube

理解分配:栈和堆

你的程序中有两种内存,栈内存和堆内存。

go 中,每个 go 程都会有一个栈空间,整个程序有一个堆空间。

变量是在栈还是堆上

负责堆垃圾回收的 GC 会导致整个程序的延迟,而不仅仅是创建垃圾的部分。你可能会担心你的代码在堆中产生了多少垃圾。

什么时候需要优化

要有 benchmarks 基准来证明你的程序不够快(有大量的堆内存分配),够快就不用多此一举了。

你要先确保程序能正确运作(业务处理),而不是先着重性能优化。

在有大量指针+运行快速的程序、清晰明了+但有点慢的程序之间,你应该选择后者。

普通类型参数传递

第5行进入squre函数

函数和局部变量被挤压入栈,一个函数为一个堆栈帧

squre函数执行完

执行完成后,你会发现黑线(只是用于区分)向上移,上方内容为有效内容,下方内容为无效内容(过期而不再使用

第6行执行println函数

go 声明了新的内存部分,我们有了新的堆栈帧用于打印行,黑线下移。

通俗的来讲,栈空间会进行自我清理,任何变量都会被清理干净,空间会被重复使用。

指针类型参数传递

第4行声明变量

main 函数和变量 n 被压入栈。

第5行进入inc函数

inc 函数和变量 x 被压入栈,属于另一个堆栈帧,黑线下移。传入的变量是 n 的地址。

inc函数执行完

黑线上移,n 被修改了值。并没有什么问题。

执行println函数

总结

虽然使用了指针传参,但这种情况下它能够留在栈上,即共享向下传递时,通常留在栈空间上。(主函数将自己的变量传给子函数)

函数返回引用

第4行进入answer函数前

编译器推断出 n 是 int 的指针类型,赋初值 nil 等待函数返回结果。

进入函数

在函数内部声明了变量并初始化,并返回了变量的内存地址。

执行println函数

致命的问题

你会发现,我明明没有进行赋值修改的操作,但却破坏了原有的值。原因在于

  • 调用 answer 函数的时候,假如我们把声明的变量 x 放在栈空间,返回变量的地址。
  • 那么 main 函数中的 n 会拿到 x 的地址,当 answer 方法退出时(黑线上移),该堆栈帧已过期,但是 n 还引用着 x
  • 我们知道堆栈的空间是可以被重复利用的,那么下一次执行其他函数或者声明变量的时候,那块过期的内存区域就会被重新使用,修改为其他值。
  • 案例中调用 println,黑线下移,产生新的堆栈帧,原先 x 标识符被替换成了 a,修改 a 的值等同修改了 n 地址解引用后的值。这是非常致命的错误。

解决的方法

为了解决上述致命的问题,被声明初始化的 x 变量就需要“逃逸”到堆空间中。这样其他无关函数就不会对它的值产生影响。

共享向上传递时,通常留在堆空间上。(子函数将自己的变量传给主函数,引用)

Escape Analysis

编译器怎么判断的

变量只在函数里工作,那就分配到栈空间上。如果编译器不能证明函数返回后声明过的变量是否被引用,那必须将其分配到堆空间上。

询问编译器

查看构建指令,可以提供一个 -gcflags 的可选参数,传递给 go tool compile 工具

查询 go tool compile -h,得知参数 -m 可以输出编译器的优化决定(变量放在栈还是堆上)

执行 go build -gcflags "-m" 获取到下面的结果

总结

什么时候会把变量分配在堆内存上

  • 函数返回退出后声明过的变量依旧被引用着。
  • 变量初始化值大小过大无法分配进栈。
  • 不知道变量值的具体大小,比如切片。

做个判断

看看前面总结的第一点和第三点。明显可以知道右边那个是分配在栈上,而左边那个分配在堆上。

思考io.Reader接口

io 标准库里有个 Reader 接口,你应该知道为什么官方要用前者替换后者了吧。如果使用后者,会在堆上产生大量的垃圾,造成程序迟钝。前者则符合向下传递思想,变量通常分配在栈空间上。

最后

不要猜想,多用工具!

GopherCon SG 2019 "Understanding Allocations" 学习笔记的更多相关文章

  1. 2019暑假Java学习笔记(二)

    目录 基础语法(下) 流程控制 if语句 switch语句 while语句和do-while语句 for语句 break关键字 continue关键字 数组 一维数组 二维数组 用户输入操作 练习题: ...

  2. 2019暑假Java学习笔记(一)

    目录 基础语法(上) HelloWorld 变量 常量 数据类型 整数 浮点数 char类型 boolean类型 String 计算字符串长度 字符串比较 字符串连接 charAt()方法 字符串常用 ...

  3. 2019暑假Java学习笔记(三)

    目录 面向对象 对象 构造方法 引用与对象实例 static final 封装 this 继承 super 方法重载与重写 多态 抽象类 接口 内部类 成员内部类 静态内部类 局部内部类 匿名内部类 ...

  4. Tensorflow学习笔记2019.01.22

    tensorflow学习笔记2 edit by Strangewx 2019.01.04 4.1 机器学习基础 4.1.1 一般结构: 初始化模型参数:通常随机赋值,简单模型赋值0 训练数据:一般打乱 ...

  5. Tensorflow学习笔记2019.01.03

    tensorflow学习笔记: 3.2 Tensorflow中定义数据流图 张量知识矩阵的一个超集. 超集:如果一个集合S2中的每一个元素都在集合S1中,且集合S1中可能包含S2中没有的元素,则集合S ...

  6. AngularJs学习笔记--Understanding the Model Component

    原版地址:http://docs.angularjs.org/guide/dev_guide.mvc.understanding_model 在angular文档讨论的上下文中,术语“model”可以 ...

  7. AngularJs学习笔记--Understanding the Controller Component

    原版地址:http://docs.angularjs.org/guide/dev_guide.mvc.understanding_model 在angular中,controller是一个javasc ...

  8. AngularJs学习笔记--Understanding Angular Templates

    原版地址:http://docs.angularjs.org/guide/dev_guide.mvc.understanding_model angular template是一个声明规范,与mode ...

  9. 2019国家集训队论文《整点计数》命题报告 学习笔记/Min25

    \(2019\)国家集训队论文<整点计数>命题报告 学习笔记/\(Min25\) 补了个大坑 看了看提交记录,发现\(hz\)的\(xdm\)早过了... 前置知识,\(HAOI\)< ...

随机推荐

  1. windows服务器怎么将证书添加到受信任证书颁发机构

    1.键盘输入win+r 快键键,出现运行,输入mmc. 2.打开控制台根节点,点击上方导航栏的文件-->添加删除管理单元.如下图. 3.在可用的管理单元中选择"证书",计算机 ...

  2. 使用C#语言,如何实现EPLAN二次开发 Api插件及菜单展示

    上期我们谈谈了谈EPLAN电气制图二次开发,制图软件EPLAN的安装和破解,今天我们来说说使用C#语言,如何实现Api插件及菜单,今天它来了!!! 关于项目环境的搭建请参考:https://blog. ...

  3. C++中的RAII介绍

    摘要 RAII技术被认为是C++中管理资源的最佳方法,进一步引申,使用RAII技术也可以实现安全.简洁的状态管理,编写出优雅的异常安全的代码. 资源管理 RAII是C++的发明者Bjarne Stro ...

  4. Java代码查错部分?

    1. abstract class Name { private String name; public abstract boolean isStupidName(String name) {} } ...

  5. Dubbo 和 Dubbox 之间的区别?

    Dubbox 是继 Dubbo 停止维护后,当当网基于 Dubbo 做的一个扩展项目,如 加了服务可 Restful 调用,更新了开源组件等.

  6. 什么是 NetflixFeign?它的优点是什么?

    Feign 是受到 Retrofit,JAXRS-2.0 和 WebSocket 启发的 java 客户端联编程序.Feign 的第一个目标是将约束分母的复杂性统一到 http apis,而不考虑其稳定 ...

  7. RabbitMQ 的集群?

    镜像集群模式 你创建的 queue,无论元数据还是 queue 里的消息都会存在于多个实例上,然后每次你写消息到 queue 的时候,都会自动把消息到多个实例的 queue 里进行消息同步. 好处在于 ...

  8. spring-boot-learning-事务处理

     事务处理的重要性: 面对高井发场景, 掌握数据库事务机制是至关重要的,它能够帮助我们在一定程度上保证数据的一致性,并且有效提高系统性能,避免系统产生岩机,这对于互联网企业应用的成败是至关重要的. 以 ...

  9. Effective Java —— 优先考虑依赖注入来引用资源

    本文参考 本篇文章参考自<Effective Java>第三版第五条"Prefer dependency injection to hardwiring resources&qu ...

  10. 3.3V转5V原理图