逃逸分析与栈、堆分配分析 escape_analysis
小结:
1、当形参为 interface 类型时,在编译阶段编译器无法确定其具体的类型。因此会产生逃逸,最终分配到堆上。
2、The construction of a value doesn’t determine where it lives. Anytime you share a value up the call stack, it is going to escape.
3、
However, if the compiler cannot prove that the variable is not referenced after the function returns, then the compiler must allocate the variable on the garbage-collected heap to avoid dangling pointer errors.
Frequently Asked Questions (FAQ) - The Go Programming Language https://golang.org/doc/faq#stack_or_heap
How do I know whether a variable is allocated on the heap or the stack? ¶
From a correctness standpoint, you don't need to know. Each variable in Go exists as long as there are references to it. The storage location chosen by the implementation is irrelevant to the semantics of the language.
The storage location does have an effect on writing efficient programs. When possible, the Go compilers will allocate variables that are local to a function in that function's stack frame. However, if the compiler cannot prove that the variable is not referenced after the function returns, then the compiler must allocate the variable on the garbage-collected heap to avoid dangling pointer errors. Also, if a local variable is very large, it might make more sense to store it on the heap rather than the stack.
In the current compilers, if a variable has its address taken, that variable is a candidate for allocation on the heap. However, a basic escape analysis recognizes some cases when such variables will not live past the return from the function and can reside on the stack.
The construction of a value doesn’t determine where it lives. Only how a value is shared will determine what the compiler will do with that value. Anytime you share a value up the call stack, it is going to escape. There are other reasons for a value to escape which you will explore in the next post.
What these posts are trying to lead you to is guidelines for choosing value or pointer semantics for any given type. Each semantic comes with a benefit and cost. Value semantics keep values on the stack which reduces pressure on the GC. However, there are different copies of any given value that must be stored, tracked and maintained. Pointer semantics place values on the heap which can put pressure on the GC. However, they are efficient because there is only one value that needs to be stored, tracked and maintained. The key is using each semantic correctly, consistently and in balance.
golang 逃逸分析与栈、堆分配分析_惜暮-CSDN博客 https://louyuting.blog.csdn.net/article/details/102846449
golang 逃逸分析与栈、堆分配分析
我们在写 golang 代码时候定义变量,那么一个很常见的问题,申请的变量保存在哪里呢?栈?还是堆?会不会有一些特殊例子?这篇文章我们就来探索下具体的case以及如何做分析。
还是从实际使用场景出发:
Question
package main
type User struct {
ID int64
Name string
Avatar string
}
func GetUserInfo() *User {
return &User{
ID: 666666,
Name: "sim lou",
Avatar: "https://www.baidu.com/avatar/666666",
}
}
func main() {
u := GetUserInfo()
println(u.Name)
}
这里GetUserInfo 函数里面的 User 对象是存储在函数栈上还是堆上?
什么是堆?什么是栈?
简单说:
- 堆:一般来讲是人为手动进行管理,手动申请、分配、释放。一般所涉及的内存大小并不定,一般会存放较大的对象。另外其分配相对慢,涉及到的指令动作也相对多
- 栈:由编译器进行管理,自动申请、分配、释放。一般不会太大,我们常见的函数参数(不同平台允许存放的数量不同),局部变量等等都会存放在栈上
今天我们介绍的 Go 语言,它的堆栈分配是通过 Compiler 进行分析,GC 去管理的,而对其的分析选择动作就是今天探讨的重点
逃逸分析
逃逸分析是一种确定指针动态范围的方法,简单来说就是分析在程序的哪些地方可以访问到该指针。
通俗地讲,逃逸分析就是确定一个变量要放堆上还是栈上,规则如下:
- 是否有在其他地方(非局部)被引用。只要有可能被引用了,那么它一定分配到堆上。否则分配到栈上
- 即使没有被外部引用,但对象过大,无法存放在栈区上。依然有可能分配到堆上
对此你可以理解为,逃逸分析是编译器用于决定变量分配到堆上还是栈上的一种行为。
在什么阶段确立逃逸
go 在编译阶段确立逃逸,注意并不是在运行时
为什么需要逃逸
其实就是为了尽可能在栈上分配内存,我们可以反过来想,如果变量都分配到堆上了会出现什么事情?例如:
- 垃圾回收(GC)的压力不断增大
- 申请、分配、回收内存的系统开销增大(相对于栈)
- 动态分配产生一定量的内存碎片
其实总的来说,就是频繁申请、分配堆内存是有一定 “代价” 的。会影响应用程序运行的效率,间接影响到整体系统。因此 “按需分配” 最大限度的灵活利用资源,才是正确的治理之道。这就是为什么需要逃逸分析的原因,你觉得呢?
go怎么确定是否逃逸
第一:编译器命令
可以看到详细的逃逸分析过程。而指令集 -gcflags 用于将标识参数传递给 Go 编译器,涉及如下:
- -m 会打印出逃逸分析的优化策略,实际上最多总共可以用 4 个 -m,但是信息量较大,一般用 1 个就可以了
- -l 会禁用函数内联,在这里禁用掉 inline 能更好的观察逃逸情况,减少干扰
$ go build -gcflags '-m -l' main.go
第二:反编译命令查看
$ go tool compile -S main.go
注:可以通过 go tool compile -help 查看所有允许传递给编译器的标识参数
实际案例
1.指针
package main
type User struct {
ID int64
Name string
Avatar string
}
func GetUserInfo() *User {
return &User{
ID: 666666,
Name: "sim lou",
Avatar: "https://www.baidu.com/avatar/666666",
}
}
func main() {
u := GetUserInfo()
println(u.Name)
}
看编译器命令执行结果:
$go build -gcflags '-m -l' escape_analysis.go
# command-line-arguments
./escape_analysis.go:13:11: &User literal escapes to heap
通过查看分析结果,可得知 &User 逃到了堆里,也就是分配到堆上了。这是不是有问题啊…再看看汇编代码确定一下,如下:
$go tool compile -S escape_analysis.go
"".GetUserInfo STEXT size=190 args=0x8 locals=0x18
0x0000 00000 (escape_analysis.go:9) TEXT "".GetUserInfo(SB), ABIInternal, $24-8
......
0x002c 00044 (escape_analysis.go:13) CALL runtime.newobject(SB)
......
0x0045 00069 (escape_analysis.go:12) CMPL runtime.writeBarrier(SB), $0
0x004c 00076 (escape_analysis.go:12) JNE 156
0x004e 00078 (escape_analysis.go:12) LEAQ go.string."sim lou"(SB), CX
......
0x0061 00097 (escape_analysis.go:13) CMPL runtime.writeBarrier(SB), $0
0x0068 00104 (escape_analysis.go:13) JNE 132
0x006a 00106 (escape_analysis.go:13) LEAQ go.string."https://www.baidu.com/avatar/666666"(SB), CX
......
执行了 runtime.newobject 方法,也就是确实是分配到了堆上。这是为什么呢?这是因为 GetUserInfo() 返回的是指针对象,引用被返回到了方法之外了。因此编译器会把该对象分配到堆上,而不是栈上。否则方法结束之后,局部变量就被回收了,岂不是翻车。所以最终分配到堆上是理所当然的。
那么所有的指针都在堆上?也不是:
func PrintStr() {
str := new(string)
*str = "hello world"
}
func main() {
PrintStr()
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
看编译器逃逸分析的结果:
$go build -gcflags '-m -l' escape_analysis3.go
# command-line-arguments
./escape_analysis3.go:4:12: PrintStr new(string) does not escape
- 1
- 2
- 3
看,该对象分配到栈上了。很核心的一点就是它有没有被作用域之外所引用,而这里作用域仍然保留在 main 中,因此它没有发生逃逸。
2. 不确定类型
func main() {
str := new(string)
*str = "hello world"
fmt.Println(*str)
}
- 1
- 2
- 3
- 4
- 5
执行命令观察一下,如下:
$go build -gcflags '-m -l' escape_analysis4.go
# command-line-arguments
./escape_analysis4.go:6:12: main new(string) does not escape
./escape_analysis4.go:8:13: main ... argument does not escape
./escape_analysis4.go:8:14: *str escapes to heap
- 1
- 2
- 3
- 4
- 5
通过查看分析结果,可得知 str 变量逃到了堆上,也就是该对象在堆上分配。但上个案例时它还在栈上,我们也就 fmt 输出了它而已。这…到底发生了什么事?
相对案例一,案例二只加了一行代码 fmt.Println(str),问题肯定出在它身上。其原型:func Println(a ...interface{}) (n int, err error)
通过对其分析,可得知当形参为 interface 类型时,在编译阶段编译器无法确定其具体的类型。因此会产生逃逸,最终分配到堆上。
如果你有兴趣追源码的话,可以看下内部的 reflect.TypeOf(arg).Kind() 语句,其会造成堆逃逸,而表象就是 interface 类型会导致该对象分配到堆上。
总结
- 静态分配到栈上,性能一定比动态分配到堆上好
- 底层分配到堆,还是栈。实际上对你来说是透明的,不需要过度关心
- 每个 Go 版本的逃逸分析都会有所不同(会改变,会优化)
- 直接通过 go build -gcflags ‘-m -l’ 就可以看到逃逸分析的过程和结果
- 到处都用指针传递并不一定是最好的,要用对。
Language Mechanics On Escape Analysis https://www.ardanlabs.com/blog/2017/05/language-mechanics-on-escape-analysis.html
逃逸分析与栈、堆分配分析 escape_analysis的更多相关文章
- (转)java内存分配分析/栈内存、堆内存
转自(http://blog.csdn.net/qh_java/article/details/9084091) java内存分配分析/栈内存.堆内存 java内存分配分析 本文将由浅入深详细介绍Ja ...
- C#中堆和栈的区别分析
线程堆栈:简称栈 Stack托管堆: 简称堆 Heap 使用.Net框架开发程序的时候,我们无需关心内存分配问题,因为有GC这个大管家给我们料理一切.如果我们写出如下两段代码: 1 代码段1: 2 3 ...
- C#中堆和栈的区别分析(有待更新总结2)
转载:http://blog.csdn.net/Zevin/article/details/5731965 线程堆栈:简称栈 Stack 托管堆: 简称堆 Heap 使用.Net框架开发程序的时候,我 ...
- Java内存分析--栈--堆
Java内存分析--栈--堆 JVM的内存分析: 1.栈内存 1.连续的存储空间,遵循后进先出的原则. 2.每个线程包含一个栈区,栈区只保存基础数据类型的对象和自定义对象的引用. 3.每个栈中的数据都 ...
- 如何限制一个类只在堆上分配和栈上分配(StackOnly HeapOnly)
[本文链接] http://www.cnblogs.com/hellogiser/p/stackonly-heaponly.html [题目] 如何限制一个类只在堆上分配和栈上分配? [代码] C+ ...
- [转]使用Java Mission Control进行内存分配分析
jdk7u40自带了一个非常好用的工具,就是Java Mission Control.JRockit Misson Control用户应该会对mission control的很多功能十分熟悉,JRoc ...
- 如何让对象只在堆或者栈中分配空间ANDC++禁止一个类被继承
在开始之前先来分析一下C++中的new运算符和operator new之间的关联. new:指我们在C++里通常用到的运算符,比如A* a = new A或者调用带参数的构造函数; 对于new来说, ...
- 栈 堆 stack heap 堆内存 栈内存 内存分配中的堆和栈 掌握堆内存的权柄就是返回的指针 栈是面向线程的而堆是面向进程的。 new/delete and malloc/ free 指针与内存模型
小结: 1.栈内存 为什么快? Due to this nature, the process of storing and retrieving data from the stack is ver ...
- 源码分析:Java堆的创建
虚拟机在内存中申请一片区域,由虚拟机自动管理,用来满足应用程序对象分配的空间需求,即堆空间. 由于程序运行的局部特性,程序创建的大多数对象都具有非常短的生命周期,而程序也会创建一些生命周期特别长的对象 ...
随机推荐
- AspectJ之@DeclareParents注解为对象添加新方法
众所周知,AspectJ可以通过@Before,@After,@Around等注解对连接点进行增强,今天我们来玩一个新注解@DeclareParents.对目标对象增强一个新方法. 场景引入: 现在我 ...
- easyui中权限分配和添加 前后端代码
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...
- 前端网页打印插件print.js(可导出pdf)
在前端开发中,想打印当前网页的指定区域内容,或将网页导出为多页的PDF,可以借助print.js实现,该插件轻量.简单.手动引入.不依赖其他库.示范项目github:https://github.co ...
- leetcode 274H-index
public int hIndex(int[] citations) { /* 唠唠叨叨说了很多 其实找到一个数h,使得数组中至少有h个数大于等于这个数, 其他N-h个数小于这个数,h可能有多个,求最 ...
- 记一次由于引用第三方服务导致的GC overhead limit exceeded异常
最近笔者遇到一个问题 监控平台忽然告警 GC overhead limit exceeded 这个异常 第一反应估计是堆溢出了.于是各种各种jmap jstack下载堆栈文件和堆日志文件. 以下是 ...
- 初探JAVA内部类细节一
定义: 可以将一个类的定义放在另一个类的内部 这就是内部类.--摘自java编程思想 一般实现方式: public class SimpleInnerClass { class Content { p ...
- C++编译过程概述
一 ---导读 想象成工厂要产出一个产品的过程,经过流水线上一步一步,不同的人的操作,然后经过整合,就得到了一个完整可用的产品. 二---编译过程图解 三---在linux中编程详解编译过程 当我们在 ...
- Interface注意事项
Interface 成员声明 声明属性,默认static & final 声明方法,默认public interface Instrument { int VALUE = 5; // stat ...
- 线程安全问题synchronized锁
1.线程安全问题的原因 因为 Java 中对静态变量的自增,自减并不是原子操作,要彻底理解,必须从字节码来进行分析 例如对于 i++ 而言(i 为静态变量),实际会产生如下的 JVM 字节码指令: ...
- DEDECMS自动编号(序号)autoindex属性(转)
版权声明:本文为博主原创文章,未经博主允许不得转载. 让织梦dedecms autoindex,itemindex 从0到1开始的办法! 1 2 3 [field:global name=autoin ...