Golang之变量去哪儿

写过C/C++的同学都知道,调用著名的malloc和new函数可以在堆上分配一块内存,这块内存的使用和销毁的责任都在程序员。一不小心,就会发生内存泄露,搞得胆战心惊。

切换到Golang后,基本不会担心内存泄露了。虽然也有new函数,但是使用new函数得到的内存不一定就在堆上。堆和栈的区别对程序员“模糊化”了,当然这一切都是Go编译器在背后帮我们完成的。

一个变量是在堆上分配,还是在栈上分配,是经过编译器的逃逸分析之后得出的结论。

这篇文章,就将带领大家一起去探索逃逸分析——变量到底去哪儿,堆还是栈?


01什么是逃逸分析


以前写C/C++代码时,为了提高效率,常常将pass-by-value(传值)“升级”成pass-by-reference,企图避免构造函数的运行,并且直接返回一个指针。

你一定还记得,这里隐藏了一个很大的坑:在函数内部定义了一个局部变量,然后返回这个局部变量的地址(指针)。这些局部变量是在栈上分配的(静态内存分配),一旦函数执行完毕,变量占据的内存会被销毁,任何对这个返回值作的动作(如解引用),都将扰乱程序的运行,甚至导致程序直接崩溃。比如下面的这段代码:

  1. int *foo ( void )   int *foo ( void )  

  2. {   {  

  3.    int t = 3;    int t = 3;

  4.    return &t;    return &t;

  5. } }

有些同学可能知道上面这个坑,用了个更聪明的做法:在函数内部使用new函数构造一个变量(动态内存分配),然后返回此变量的地址。因为变量是在堆上创建的,所以函数退出时不会被销毁。但是,这样就行了吗?new出来的对象该在何时何地delete呢?调用者可能会忘记delete或者直接拿返回值传给其他函数,之后就再也不能delete它了,也就是发生了内存泄露。关于这个坑,大家可以去看看《Effective C++》条款21,讲得非常好!

C++是公认的语法最复杂的语言,据说没有人可以完全掌握C++的语法。而这一切在Go语言中就大不相同了。像上面示例的C++代码放到Go里,没有任何问题。

你表面的光鲜,一定是背后有很多人为你撑起的!Go语言里就是编译器的逃逸分析。它是编译器执行静态代码分析后,对内存管理进行的优化和简化。

在编译原理中,分析指针动态范围的方法称之为逃逸分析。通俗来讲,当一个对象的指针被多个方法或线程引用时,我们称这个指针发生了逃逸。

更简单来说,逃逸分析决定一个变量是分配在堆上还是分配在栈上。


02为什么要逃逸分析


前面讲的C/C++中出现的问题,在Go中作为一个语言特性被大力推崇。真是C/C++之砒霜Go之蜜糖!

C/C++中动态分配的内存需要我们手动释放,导致猿们平时在写程序时,如履薄冰。这样做有他的好处:程序员可以完全掌控内存。但是缺点也是很多的:经常出现忘记释放内存,导致内存泄露。所以,很多现代语言都加上了垃圾回收机制。

Go的垃圾回收,让堆和栈对程序员保持透明。真正解放了程序员的双手,让他们可以专注于业务,“高效”地完成代码编写。把那些内存管理的复杂机制交给编译器,而程序员可以去享受生活。

逃逸分析这种“骚操作”把变量合理地分配到它该去的地方,“找准自己的位置”。即使你是用new申请到的内存,如果我发现你竟然在退出函数后没有用了,那么就把你丢到栈上,毕竟栈上的内存分配比堆上快很多;反之,即使你表面上只是一个普通的变量,但是经过逃逸分析后发现在退出函数之后还有其他地方在引用,那我就把你分配到堆上。真正地做到“按需分配”,提前实现共产主义!

如果变量都分配到堆上,堆不像栈可以自动清理。它会引起Go频繁地进行垃圾回收,而垃圾回收会占用比较大的系统开销(占用CPU容量的25%)。

堆和栈相比,堆适合不可预知大小的内存分配。但是为此付出的代价是分配速度较慢,而且会形成内存碎片。栈内存分配则会非常快。栈分配内存只需要两个CPU指令:“PUSH”和“RELEASSE”,分配和释放;而堆分配内存首先需要去找到一块大小合适的内存块,之后要通过垃圾回收才能释放。

通过逃逸分析,可以尽量把那些不需要分配到堆上的变量直接分配到栈上,堆上的变量少了,会减轻分配堆内存的开销,同时也会减少gc的压力,提高程序的运行速度。


03逃逸分析是怎么完成的


Go逃逸分析最基本的原则是:如果一个函数返回对一个变量的引用,那么它就会发生逃逸。

简单来说,编译器会分析代码的特征和代码生命周期,Go中的变量只有在编译器可以证明在函数返回后不会再被引用的,才分配到栈上,其他情况下都是分配到堆上。

Go语言里没有一个关键字或者函数可以直接让变量被编译器分配到堆上,相反,编译器通过分析代码来决定将变量分配到何处。

对一个变量取地址,可能会被分配到堆上。但是编译器进行逃逸分析后,如果考察到在函数返回后,此变量不会被引用,那么还是会被分配到栈上。套个取址符,就想骗补助?Too young!

简单来说,编译器会根据变量是否被外部引用来决定是否逃逸:

  1. 如果函数外部没有引用,则优先放到栈中;

  2. 如果函数外部存在引用,则必定放到堆中;

针对第一条,可能放到堆上的情形:定义了一个很大的数组,需要申请的内存过大,超过了栈的存储能力。


04逃逸分析实例



Go提供了相关的命令,可以查看变量是否发生逃逸。

还是用上面我们提到的例子:

  1. package mainpackage main

  2. import "fmt"import "fmt"

  3. func foo() *int {func foo() *int {

  4.    t := 3    t := 3

  5.    return &t;    return &t;

  6. }}

  7. func main() {func main() {

  8.    x := foo()    x := foo()

  9.    fmt.Println(*x)    fmt.Println(*x)

  10. }}

foo函数返回一个局部变量的指针,main函数里变量x接收它。执行如下命令:

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

-l是为了不让foo函数被内联。得到如下输出:

  1. # command-line-arguments# command-line-arguments

  2. src/main.go:7:9: &t escapes to heapsrc/main.go:7:9: &t escapes to heap

  3. src/main.go:6:7: moved to heap: tsrc/main.go:6:7: moved to heap: t

  4. src/main.go:12:14: *x escapes to heapsrc/main.go:12:14: *x escapes to heap

  5. src/main.go:12:13: main ... argument does not escapesrc/main.go:12:13: main ... argument does not escape

foo函数里的变量t逃逸了,和我们预想的一致。让我们不解的是为什么main函数里的x也逃逸了?这是因为有些函数参数为interface类型,比如fmt.Println(a ...interface{}),编译期间很难确定其参数的具体类型,也会发生逃逸。

使用反汇编命令也可以看出变量是否发生逃逸。

  1. go tool compile -S main.gogo tool compile -S main.go

截取部分结果,图中标记出来的说明 t是在堆上分配内存,发生了逃逸。

640?wx_fmt=png


05总结



堆上动态分配内存比栈上静态分配内存,开销大很多。

变量分配在栈上需要能在编译期确定它的作用域,否则会分配到堆上。

Go编译器会在编译期对考察变量的作用域,并作一系列检查,如果它的作用域在运行期间对编译器一直是可知的,那么就会分配到栈上。

简单来说,编译器会根据变量是否被外部引用来决定是否逃逸。对于Go程序员来说,编译器的这些逃逸分析规则不需要掌握,我们只需通过go build-gcflags'-m'命令来观察变量逃逸情况就行了。

不要盲目使用变量的指针作为函数参数,虽然它会减少复制操作。但其实当参数为变量自身的时候,复制是在栈上完成的操作,开销远比变量逃逸后动态地在堆上分配内存少的多。

最后,尽量写出少一些逃逸的代码,提升程序的运行效率。


06参考资料



【逃逸是怎么发生的】https://www.do1618.com/archives/1328/go-%E5%86%85%E5%AD%98%E9%80%83%E9%80%B8%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/

【Go的变量到底在堆还是栈中分配】https://github.com/developer-learning/night-reading-go/blob/master/content/discuss/2018-07-09-make-new-in-go.md

【Golang堆栈的理解】https://segmentfault.com/a/1190000017498101

【逃逸分析 编写栈分配内存建议】https://segment.com/blog/allocation-efficiency-in-high-performance-go-services/ 【逃逸分析 比较简洁】https://studygolang.com/articles/17584

【逃逸分析定义】https://cloud.tencent.com/developer/article/1117410

【逃逸分析例子】https://my.oschina.net/renhc/blog/2222104

https://gocn.vip/article/355 【汇编代码 传参】https://github.com/maniafish/aboutgo/blob/master/heapstack.md

【逃逸分析的缺陷】https://studygolang.com/articles/12396

【比较好的逃逸分析的例子】http://www.agardner.me/golang/garbage/collection/gc/escape/analysis/2015/10/18/go-escape-analysis.html



640?wx_fmt=png



本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/242116.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

运营商ip映射_我们如何映射互联网以发现运营商

运营商ip映射Being able to accurately predict which carriers use which IP addresses is important for Wandera’s data cost management solution. Customers with dual-SIM/eSIM devices in their fleet need to be aware at which point in time a device is using whic…

在县城开一家彩票站,一个月能赚多少钱?

现在彩票店多如牛毛,几步就有一个投注站,真能赚大钱的很少,但维持个基本生活应该是不成问题的。 至于接手彩票上是否能赚钱,关键还是要看人流,人流,人流。 想要知道彩票站是否赚钱,你就得先了解…

修改TrustedInstaller权限文件(无法删除文件)

在Win7系统中,存在一个虚拟账户,即TrustedInstaller,有时需要对C盘一些系统文件/文件夹进行修改,或删除,就会弹出“你需要TrustedInstaller提供的权限才能修改此文件”。这时用此法可解除此限制。对于系统中一些无法删…

yolov3算法优点缺点_优点缺点

yolov3算法优点缺点Naive Bayes: A classification algorithm under a supervised learning group based on Probabilistic logic. This is one of the simplest machine learning algorithms of all. Logistic regression is another classification algorithm that models po…

为什么很多企业要跑到美国去上市,而不是在A股上市?

我们都知道目前很多中国优质的企业都选择在香港,美国等境外上市,其中不乏阿里巴巴、腾讯,京东,百度这样的知名企业。比如下图是2017年我国市值排名前20的企业,这些企业当中有19个在境外上市,有的是境外跟境…

逻辑回归画图_逻辑回归

逻辑回归画图申请流程 (Application Flow) Logistic Regression is one of the most fundamental algorithms for classification in the Machine Learning world.Logistic回归是机器学习世界中分类的最基本算法之一。 But before proceeding with the algorithm, let’s firs…

邮储银行的规模有多大?凭什么可以成为第6大国有银行?

邮储银行之所以被划为第6大国有银行,因为他不论是在性质上还是在规模上都对得起第6大国有银行这一称号。首先邮储银行是国有控股的大型商业银行。邮储银行是由原来邮局的储蓄所以及邮电系统的储蓄业务整合而来,在上市之前邮储银行由中国邮政集团100%控股…

工商银行信用卡如何通过刷星提额?

想要刷星级提额,我们就先来了解一下,为什么银行愿意给你提额。不论是对其他银行还是对于工商银行来说,他们愿意给你挑提额无非就两个核心前提,一个是你能给银行创造更多的收益,第2个是你没有任何风险,也就是…

主成分分析具体解释_主成分分析-现在用您自己的术语解释

主成分分析具体解释The caption in the online magazine “WIRED” caught my eye one night a few months ago. When I focused my eyes on it, it read: “Can everything be explained to everyone in terms they can understand? In 5 Levels, an expert scientist explai…

MongoDB介绍

一、MongoDB介绍 1.1 mongoDB介绍 MongoDB 是由C语言编写的,是一个基于分布式文件存储的开源数据库系统。 在高负载的情况下,添加更多的节点,可以保证服务器性能。 MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案。 MongoDB …

Cross-Drone Transformer Network for Robust Single Object Tracking论文阅读笔记

Cross-Drone Transformer Network for Robust Single Object Tracking论文阅读笔记 Abstract 无人机在各种应用中得到了广泛使用,例如航拍和军事安全,这得益于它们与固定摄像机相比的高机动性和广阔视野。多无人机追踪系统可以通过从不同视角收集互补的…

【5G PHY】NR参考信号功率和小区总传输功率的计算

博主未授权任何人或组织机构转载博主任何原创文章,感谢各位对原创的支持! 博主链接 本人就职于国际知名终端厂商,负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作,目前牵头6G算力网络技术标准研究。 博客…

2016年第五届数学建模国际赛小美赛A题臭氧消耗预测解题全过程文档及程序

2016年第五届数学建模国际赛小美赛 A题 臭氧消耗预测 原题再现: 臭氧消耗包括自1970年代后期以来观察到的若干现象:地球平流层(臭氧层)臭氧总量稳步下降,以及地球极地附近平流层臭氧(称为臭氧空洞&#x…

数据结构和算法-二叉排序树(定义 查找 插入 删除 时间复杂度)

文章目录 二叉排序树总览二叉排序树的定义二叉排序树的查找二叉排序树的插入二叉排序树的构造二叉排序树的删除删除的是叶子节点删除的是只有左子树或者只有右子树的节点删除的是有左子树和右子树的节点 查找效率分析查找成功查找失败 小结 二叉排序树 总览 二叉排序树的定义 …

【LeetCode:1954. 收集足够苹果的最小花园周长 | 等差数列 + 公式推导】

🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,…

C语言—每日选择题—Day60

指针相关博客 打响指针的第一枪:指针家族-CSDN博客 深入理解:指针变量的解引用 与 加法运算-CSDN博客 第一题 1. 下列for循环的循环体执行次数为() for(int i 10, j 1; i j 0; i, --j) A:0 B:1 C&#…

蓝桥小课堂-平方和【算法赛】

问题描述 蓝桥小课堂开课啦! 平方和公式是一种用于计算连续整数的平方和的数学公式。它可以帮助我们快速求解从 1 到 n 的整数的平方和,其中 n 是一个正整数。 平方和公式的表达式如下: 这个公式可以简化计算过程,避免逐个计算…

2024年【制冷与空调设备运行操作】免费试题及制冷与空调设备运行操作试题及解析

题库来源:安全生产模拟考试一点通公众号小程序 制冷与空调设备运行操作免费试题根据新制冷与空调设备运行操作考试大纲要求,安全生产模拟考试一点通将制冷与空调设备运行操作模拟考试试题进行汇编,组成一套制冷与空调设备运行操作全真模拟考…

【FPGA】分享一些FPGA协同MATLAB开发的书籍

在做FPGA工程师的这些年,买过好多书,也看过好多书,分享一下。 后续会慢慢的补充书评。 【FPGA】分享一些FPGA入门学习的书籍【FPGA】分享一些FPGA协同MATLAB开发的书籍 【FPGA】分享一些FPGA视频图像处理相关的书籍 【FPGA】分享一些FPGA高速…

python时间处理方法和模块

在 Python 中,有一些内置的模块和库,可以帮助我们处理日期和时间的表示、计算和转换。 1. 时间模块(time) Python 的 time 模块提供了一系列函数来处理时间相关的操作。通过这个模块,可以获取当前时间、睡眠指定时间…