Go 逃逸分析

堆和栈

要理解什么是逃逸分析会涉及堆和栈的一些基本知识,如果忘记的同学我们可以简单的回顾一下:

  • 堆(Heap):一般来讲是人为手动进行管理,手动申请、分配、释放。堆适合不可预知大小的内存分配,这也意味着为此付出的代价是分配速度较慢,而且会形成内存碎片。

  • 栈(Stack):由编译器进行管理,自动申请、分配、释放。一般不会太大,因此栈的分配和回收速度非常快;我们常见的函数参数(不同平台允许存放的数量不同),局部变量等都会存放在栈上。

栈分配内存只需要两个CPU指令:“PUSH”和“RELEASE”,分配和释放;而堆分配内存首先需要去找到一块大小合适的内存块,之后要通过垃圾回收才能释放。

通俗比喻的说,就如我们去饭馆吃饭,只需要点菜(发出申请)--》吃吃吃(使用内存)--》吃饱就跑剩下的交给饭馆(操作系统自动回收),而就如在家里做饭,大到家,小到买什么菜,每一个环节都需要自己来实现,但是自由度会大很多。

什么是逃逸分析

在编译程序优化理论中,逃逸分析是一种确定指针动态范围的方法,简单来说就是分析在程序的哪些地方可以访问到该指针。

再往简单的说,Go是通过在编译器里做逃逸分析(escape analysis)来决定一个对象放栈上还是放堆上,不逃逸的对象放栈上,可能逃逸的放堆上;即我发现变量在退出函数后没有用了,那么就把丢到栈上,毕竟栈上的内存分配和回收比堆上快很多;反之,函数内的普通变量经过逃逸分析后,发现在函数退出后变量还有在其他地方上引用,那就将变量分配在堆上。做到按需分配(哪里的人民需要我,我就往哪去~~,一个党员的呐喊)。

为何需要逃逸分析

ok,了解完各自的优缺点后,我们就可以更好的知道逃逸分析存在的目的了:

  1. 减少gc压力,栈上的变量,随着函数退出后系统直接回收,不需要gc标记后再清除。

  2. 减少内存碎片的产生。

  3. 减轻分配堆内存的开销,提高程序的运行速度。

如何确定是否逃逸

Go中通过逃逸分析日志来确定变量是否逃逸,开启逃逸分析日志:

go run -gcflags '-m -l' main.go
  • -m 会打印出逃逸分析的优化策略,实际上最多总共可以用 4 个 -m,但是信息量较大,一般用 1 个就可以了。

  • -l 会禁用函数内联,在这里禁用掉内联能更好的观察逃逸情况,减少干扰。

逃逸案例

案例一:取地址发生逃逸

package main

type UserData struct {
Name string
}

func main() {
var info UserData
info.Name = "WilburXu"
_ = GetUserInfo(info)
}

func GetUserInfo(userInfo UserData) *UserData {
return &userInfo
}

执行 go run -gcflags '-m -l' main.go 后返回以下结果:

# command-line-arguments
.\main.go::: &userInfo escapes to heap
.\main.go::: moved to heap: userInfo

GetUserInfo函数里面的变量 userInfo 逃到堆上了(分配到堆内存空间上了)。

GetUserInfo 函数的返回值为 *UserData 指针类型,然后 将值变量userInfo 的地址返回,此时编译器会判断该值可能会在函数外使用,就将其分配到了堆上,所以变量userInfo就逃逸了。

优化方案

func main() {
var info UserData
info.Name = "WilburXu"
_ = GetUserInfo(&info)
}

func GetUserInfo(userInfo *UserData) *UserData {
return userInfo
}
# command-line-arguments
.\main.go::: leaking param: userInfo to result ~r1 level=
.\main.go::: main &info does not escape

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

编译器傲娇的说:Too young,Too Cool...!

案例二 :未确定类型

package main

type User struct {
name interface{}
}

func main() {
name := "WilburXu"
MyPrintln(name)
}

func MyPrintln(one interface{}) (n int, err error) {
var userInfo = new(User)
userInfo.name = one // 泛型赋值 逃逸咯
return
}

执行 go run -gcflags '-m -l' main.go 后返回以下结果:

# command-line-arguments
./main.go::: leaking param: one
./main.go::: MyPrintln new(User) does not escape
./main.go::: name escapes to heap

这里可能有同学会好奇,MyPrintln函数内并没有被引用的便利,为什么变了name会被分配到了上呢?

上一个案例我们知道了,普通的手法想去"骗取补助",聪明灵利的编译器是不会“上当受骗的噢”;但是对于interface类型,很遗憾,编译器在编译的时候很难知道在函数的调用或者结构体的赋值过程会是怎么类型,因此只能分配到上。

优化方案

将结构体User的成员name的类型、函数MyPringLn参数one的类型改为 string,将得出:

# command-line-arguments
./main.go::: leaking param: one
./main.go::: MyPrintln new(User) does not escape

拓展分析

对于案例二的分析,我们还可以通过反编译命令go tool compile -S main.go查看,会发现如果为interface类型,main主函数在编译后会额外多出以下指令:

# main.go: -> MyPrintln(name)
0x001d (main.go:) PCDATA $, $
0x001d (main.go:) PCDATA $, $
0x001d (main.go:) LEAQ go.string."WilburXu"(SB), AX
0x0024 (main.go:) PCDATA $, $
0x0024 (main.go:) MOVQ AX, ""..autotmp_5+(SP)
0x0029 (main.go:) MOVQ $, ""..autotmp_5+(SP)
0x0032 (main.go:) PCDATA $, $
0x0032 (main.go:) LEAQ type.string(SB), AX
0x0039 (main.go:) PCDATA $, $
0x0039 (main.go:) MOVQ AX, (SP)
0x003d (main.go:) PCDATA $, $
0x003d (main.go:) LEAQ ""..autotmp_5+(SP), AX
0x0042 (main.go:) PCDATA $, $
0x0042 (main.go:) MOVQ AX, (SP)
0x0047 (main.go:) CALL runtime.convT2Estring(SB)

对于Go汇编语法不熟悉的可以参考 Golang汇编快速指南

总结

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

Go的编译器就如一个聪明的孩子一般,大多时候在逃逸分析问题上的处理都令人眼前一亮,但有时闹性子的时候处理也是非常粗糙的分析或完全放弃,毕竟这是孩子天性不是吗? 所以也需要我们在编写代码的时候多多观察,多多留意了。

参考文章

Golang escape analysis

Go 逃逸分析的更多相关文章

  1. JVM中启用逃逸分析

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

  2. JVM笔记-逃逸分析

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

  3. Go变量逃逸分析

    目录 什么是逃逸分析 为什么要逃逸分析 逃逸分析是怎么完成的 逃逸分析实例 总结 写过C/C++的同学都知道,调用著名的malloc和new函数可以在堆上分配一块内存,这块内存的使用和销毁的责任都在程 ...

  4. JVM逃逸分析

    开启逃逸分析: -server -XX:+DoEscapeAnalysis -XX:+PrintGCDetail -Xmx10m -Xms10m 关闭逃逸分析: -server -XX:-DoEsca ...

  5. 逃逸分析(Escape Analysis)

    一.什么是逃逸 逃逸是指在某个方法之内创建的对象,除了在方法体之内被引用之外,还在方法体之外被其它变量引用到:这样带来的后果是在该方法执行完毕之后,该方法中创建的对象将无法被GC回收,由于其被其它变量 ...

  6. golang逃逸分析和竞争检测

    最近在线上发现一块代码逻辑在执行N次耗时波动很大1ms~800ms,最开始以为是gc的问题,对代码进行逃逸分析,看哪些变量被分配到堆上了,后来发现是并发编程时对一个切片并发的写,导致存在竞争,类似下面 ...

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

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

  8. Go 语言机制之逃逸分析

    https://blog.csdn.net/weixin_38975685/article/details/79788254   Go 语言机制之逃逸分析 https://blog.csdn.net/ ...

  9. JVM的逃逸分析

    我们都知道Java中的对象默认都是分配到堆上,在调用栈中,只保存了对象的指针.当对象不再使用后,需要依靠GC来遍历引用树并回收内存.如果堆中对象数量太多,回收对象还有整理内存,都会会带来时间上的消耗, ...

  10. Java之JVM逃逸分析

    引言: 逃逸分析(Escape Analysis)是众多JVM技术中的一个使用不多的技术点,本文将通过一个实例来分析其使用场景. 概念 逃逸分析,是一种可以有效减少Java 程序中同步负载和内存堆分配 ...

随机推荐

  1. 【Leetcode_easy】888. Fair Candy Swap

    problem 888. Fair Candy Swap solution: class Solution { public: vector<int> fairCandySwap(vect ...

  2. web端自动化——python多线程

    Python通过两个标准库thread和threading提供对线程的支持.thread提供了低级别的.原始的线程以及一个简单的锁.threading基于Java的线程模型设计. 锁(Lock)条件变 ...

  3. Django:bootstrap table自定义查询实现

    参考:https://jalena.bcsytv.com/archives/tag/bootstrap 背景: bootstrap table在客户端分页方式下,自带有简易的搜索功能,但是功能太单一, ...

  4. DB2 索引(2)

    最近研究了一点DB2索引相关的东西,做一个总结: (1)在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构: (2)在经常用连接的列(join)上建索引,这些列主要是一些外键,可以加快连接的速 ...

  5. console.log()和alert()的区别

    一直都是知道console.log()和alert()是有区别的,但是具体有什么区别就不清楚了,后来在权威指南里注意到了说alert()具有侵入性才来查一查两者的具体区别. 查询到的区别: alert ...

  6. GSVA的使用

    GSVA的简介 Gene Set Variation Analysis,被称为基因集变异分析,是一种非参数的无监督分析方法,主要用来评估芯片核转录组的基因集富集结果.主要是通过将基因在不同样品间的表达 ...

  7. 017 Android 获取手机SIM卡序列号和读取联系人

    1.获取手机SIM卡序列号 //5.存储sim卡系列号 //5.1获取sim卡系列号 TelephonyManager manager = (TelephonyManager) getSystemSe ...

  8. [转帖]ORM框架的前世今生

    ORM框架的前世今生 https://www.cnblogs.com/7tiny/p/9551754.html 目录 一.ORM简介二.ORM的工作原理三.ORM的优缺点四.常见的ORM框架 一.OR ...

  9. 《Mysql - 在Mysql服务出现瓶颈时,有哪些“饮鸩止渴”提高性能的方法?》

    一:情景 - 业务高峰期,生产环境的 MySQL 压力太大,没法正常响应,需要短期内.临时性地提升一些性能. - 在业务高发时候,Mysql 服务压力过大,导致业务受损, 用户的开发负责人说,不管你用 ...

  10. eclise -The method onClick(View) of type new View.OnClickListener(){} must override a superclass method 在做arcgis android开发的时候,突然遇到这种错误,The method onClick(View) of type new View.OnClickListener(){} mus

    eclise -The method onClick(View) of type new View.OnClickListener(){} must override a superclass met ...