1、什么是逃逸分析

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

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

int *foo ( void)
{
int t = ;
return &t;
}

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

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

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

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

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

2、为什么要逃逸分析

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

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

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

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

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

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

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

3、逃逸分析是怎么完成的

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

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

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

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

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

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

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

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

4、逃逸分析实例

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

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

func foo() *int {
t := return &t
} func main() { x := foo() fmt.Println(*x) }

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

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

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

# command-line-arguments

src/main.go::: &t escapes to heap

src/main.go::: moved to heap: t

src/main.go::: *x escapes to heap

src/main.go::: main ... argument does not escape

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

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

go tool compile -S main.go

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

5、总结

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

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

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

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

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

转自:https://mp.weixin.qq.com/s/ashgWyb-w4fT47xX60yNFA

GO中的逃逸分析的更多相关文章

  1. JVM中启用逃逸分析

    -XX:+DoEscapeAnalysis 逃逸分析优化JVM原理我们知道java对象是在堆里分配的,在调用栈中,只保存了对象的指针.当对象不再使用后,需要依靠GC来遍历引用树并回收内存,如果对象数量 ...

  2. 深入理解Java中的逃逸分析

    在Java的编译体系中,一个Java的源代码文件变成计算机可执行的机器指令的过程中,需要经过两段编译,第一段是把.java文件转换成.class文件.第二段编译是把.class转换成机器指令的过程. ...

  3. java中的逃逸分析

    逃逸分析 public static StringBuffer craeteStringBuffer(String s1, String s2) { StringBuffer sb = new Str ...

  4. JVM中的逃逸分析

    逃逸分析(Escape Analysis)是目前Java虚拟机中比较前沿的优化技术. 逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递 ...

  5. JVM笔记-逃逸分析

    参考: http://www.iteye.com/topic/473355http://blog.sina.com.cn/s/blog_4b6047bc01000avq.html 什么是逃逸分析(Es ...

  6. 基于Golang的逃逸分析(Language Mechanics On Escape Analysis)

    何为逃逸分析 在编译程序优化理论中,逃逸分析是一种确定指针动态范围的方法——分析在程序的哪些地方可以访问到指针.它涉及到指针分析和形状分析. 当一个变量(或对象)在子程序中被分配时,一个指向变量的指针 ...

  7. Golang逃逸分析

    Golang逃逸分析 介绍逃逸分析的概念,go怎么开启逃逸分析的log. 以下资料来自互联网,有错误之处,请一定告之. sheepbao 2017.06.10 什么是逃逸分析 wiki上的定义 In ...

  8. 聊聊Golang逃逸分析

    逃逸分析的概念,go怎么开启逃逸分析的log. 以下资料来自互联网,有错误之处,请一定告之. 什么是逃逸分析 wiki上的定义 在编译程序优化理论中,逃逸分析是一种确定指针动态范围的方法——分析在程序 ...

  9. 面试问我 Java 逃逸分析,瞬间被秒杀了。。

    记得几年前有一次栈长去面试,问到了这么一个问题: Java中的对象都是在堆中分配吗?说明为什么! 当时我被问得一脸蒙逼,瞬间被秒杀得体无完肤,当时我压根就不知道他在考什么知识点,难道对象不是在堆中分配 ...

随机推荐

  1. 常用的 Git 命令与场景

    Git 分布式版本控制系统 它拥有完整的版本控制功能,能够解决多人协作的问题 将自己的代码同步到 Github 上能够提升开发效率 git 会记录你每一次的版本修改操作 常用的 Git 操作 # 指定 ...

  2. 蓝桥杯vip 字符串对比

    蓝桥杯vip 字符串对比 题目如下 给定两个仅由大写字母或小写字母组成的字符串(长度介于1到10之间),它们之间的关系是以下4中情况之一: 1:两个字符串长度不等.比如 Beijing 和 Hebei ...

  3. 分享macOS平台好用的视频分割、合并视频、提取音频、分离音频、音频转码的工具CCVideo

    CCVideo 是一款运行在macOS上可分割视频(可多段分割).合并视频.提取音频.分离音频.音频转码的工具,操作方便,只需简单几步,便可轻松完成. 下载地址

  4. 文本编辑器之kindeditor

    摘要:最近在自己学习搭建网站的时候,突然要搭建网站的时候发现了一个好东西,那就是kindeditor这个文本编辑器,这个编辑器简单好用,而且很小,并且是开源的. 文本编辑器介绍 KindEditor ...

  5. 8千字干货教程|java反射精讲

    java反射机制精讲 目录 1. 反射机制的概念 2. 反射的基础Class类 3. 反射的用法 4. 反射的应用示例 作者简介:全栈学习笔记,一个正在努力的人 微信公众号:公众号日更,精彩美文每天推 ...

  6. VMware使用总结

    1.处理器设置释疑 比如一个8核16线程处理器 处理器数量最多设置为8,而每个处理器的内核数量*处理器个数必须小于等于16. 2.虚拟网络编辑器 NAT模式中可通过NAT设置将内部端口映射到主机端口. ...

  7. Android菜单(menu)

    Android  菜单 我们继续来进行学习,今天写一下在软件中用的还算较多的菜单. 1.Menu 菜单,很显然,作用就是点击不同的选项触发不同的方法.现在在安卓使用中推荐使用ActionBar,但这里 ...

  8. 推荐 | 7个你最应该知道的机器学习相关github项目

    欢迎大家关注我们的网站和系列教程:http://www.tensorflownews.com/,学习更多的机器学习.深度学习的知识! 磐石 目录: 介绍 Person Blocker(人体自动遮挡) ...

  9. 《带你装B,带你飞》pytest成魔之路4 - fixture 之大解剖

    1. 简介 fixture是pytest的一个闪光点,pytest要精通怎么能不学习fixture呢?跟着我一起深入学习fixture吧.其实unittest和nose都支持fixture,但是pyt ...

  10. Python——铅球飞行计算问题

    一. 1.IPO描述为:输入:铅球发射角度. 初始速度(m/s). 初始高度(m) 处理:模拟铅球飞行,时刻更新铅球在飞行中的位置 输出:铅球飞行距离(m) 可以拆分小的时间段.任意时刻的位置,都是 ...