Go —— 逃逸分析

2024-03-17 04:36
文章标签 分析 go 逃逸

本文主要是介绍Go —— 逃逸分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

逃逸分析

  逃逸分析是指由编译器决定内存分配的位置,不需要程序员指定。在函数中申请一个新的对象:

  • 如果分配在栈中,则函数执行结束后可自动将内存回收
  • 如果分配在堆中,则函数执行结束后可交给GC(垃圾回收)处理

  有了逃逸分析,返回函数局部变量变得可能。除此之外,逃逸分析还跟闭包息息相关,了解哪些场景下对象会逃逸至关重要。

1. 逃逸策略

  在函数中申请新的对象时,编译器会根据该对象是否被函数外部引用来决定是否逃逸:

  • 如果函数外部没有引用,则优先放到栈中
  • 如果函数外部存在引用,则必定放在堆中

对于仅在函数内部使用的变量,也有可能放到堆中,比如内存过大超过栈的存储能力。

2. 逃逸场景

1)指针逃逸

  Go可以返回局部变量指针,这是一个典型的变量逃逸案例。示例代码如下:

package maintype Student struct {Name stringAge  int
}func StudentRegister(name string, age int) *Student {s := new(Student)s.Name = names.Age = agereturn s
}
func main() {StudentRegister("Jim", 18)
}

   函数 StudentRegister() 内部的s为局部变量,其值通过函数值返回,s本身为一个指针,其指向的内存地址不会是栈,而是堆,这就是典型的逃逸案例。
   通过编译参数 -gcflags=-m 可以查看编译过程中的逃逸分析过程:

PS D:\Go\workspace\src\lekou> go build -gcflags=-m
# lekou
./main.go:8:6: can inline StudentRegister
./main.go:14:6: can inline main
./main.go:15:17: inlining call to StudentRegister
./main.go:8:22: leaking param: name
./main.go:9:10: new(Student) escapes to heap
./main.go:15:17: new(Student) does not escape
PS D:\Go\workspace\src\lekou> 

  在 StudentRegister() 函数中,代码第9行显示 “escapes to heap” ,表示该行内存分配发生了逃逸现象。

2)栈空间不足逃逸

  当栈空间不足以存放当前对象或无法判断当前切片长度时会将对象分配到堆中。示例代码:

package mainfunc Slice() {s1 := make([]int, 1000, 1000)s2 := make([]int, 10000, 10000)for index, _ := range s1 {s1[index] = index}for index, _ := range s2 {s2[index] = index}
}
func main() {Slice()
}

  s1的长度为1000,s2的长度为10000。查看编译提示,如下:

PS D:\Go\workspace\src\lekou> go build -gcflags=-m
# lekou
./main.go:3:6: can inline Slice
./main.go:14:6: can inline main
./main.go:15:7: inlining call to Slice
./main.go:4:12: make([]int, 1000, 1000) does not escape		// s1
./main.go:5:12: make([]int, 10000, 10000) escapes to heap	// s2
./main.go:15:7: make([]int, 1000, 1000) does not escape
./main.go:15:7: make([]int, 10000, 10000) escapes to heap

  可以发现s1没有发生逃逸,s2发生逃逸。

3)动态类型逃逸

  很多函数的参数为 interface 类型,比如 fmt.Println(a …interface{}),编译期间很难确定其参数的具体类型,也会产生逃逸,如以下代码所示。

package mainimport "fmt"func main() {s := "Hello World!"fmt.Println(s)
}

  上述代码中的 s 变量只是一个 string类型变量,调用 fmt.Println()时会产生逃逸:

PS D:\Go\workspace\src\lekou> go build -gcflags=-m
# lekou
./main.go:7:13: inlining call to fmt.Println
./main.go:7:13: ... argument does not escape
./main.go:7:14: s escapes to heap

4)闭包引用对象逃逸

  某著名的开源框架实现了某个返回Fibonacci数列的函数:

func Fibonacci() func() int {a, b := 0, 1return func() int {a, b = b, a+breturn a}
}

  该函数返回一个闭包,闭包引用了函数的局部变量 a 和 b,使用时通过该函数获取闭包,然后每次执行闭包都会依次输出 Fibonacci 数列。完整的示例程序如下:

package mainimport "fmt"func Fibonacci() func() int {a, b := 0, 1return func() int {a, b = b, a+breturn a}
}func main() {f := Fibonacci()for i := 0; i < 10; i++ {fmt.Printf("Fibonacci:%d\n", f())}
}

  上述代码通过 Fibonacci()获取一个闭包,每次执行闭包就会打印一个 Fibonacci 数值。输出如下:

C:\Users\666\AppData\Local\Temp\GoLand\___293go_build_lekou_.exe
Fibonacci:1
Fibonacci:1
Fibonacci:2
Fibonacci:3
Fibonacci:5
Fibonacci:8
Fibonacci:13
Fibonacci:21
Fibonacci:34
Fibonacci:55Process finished with the exit code 0

  Fibonacci() 函数中原本属于局部变量的 a 和 b 由于闭包的引用,不得不将二者放到堆中,以致产生逃逸:

PS D:\Go\workspace\src\lekou> go build -gcflags=-m
# lekou
./main.go:3:6: can inline Slice
./main.go:10:6: can inline main
./main.go:11:7: inlining call to Slice
./main.go:4:11: make([]int, 1000, 1000) does not escape
./main.go:14:16: inlining call to Fibonacci
./main.go:7:9: can inline main.Fibonacci.func1
./main.go:17:35: inlining call to main.Fibonacci.func1
./main.go:17:13: inlining call to fmt.Printf
./main.go:6:2: moved to heap: a
./main.go:6:5: moved to heap: b
./main.go:7:9: func literal escapes to heap
./main.go:14:16: func literal does not escape
./main.go:17:13: ... argument does not escape
./main.go:17:35: ~r0 escapes to heap

3. 小结

  • 栈上分配内存比在堆中分配内存有更高的效率
  • 栈上分配内存不需要GC处理
  • 对上分配的内存使用完毕会交给GC处理
  • 逃逸分析的目的是决定分配地址是栈还是堆
  • 逃逸分析在编译阶段完成

4. 问题

  思考一下这个问题:函数传递指针真的比传值的效率高吗?

  我们知道传递指针可以减少底层值的复制,可以提高效率,但是如果复制的数据量小,由于指针传递会产生逃逸,则可能会使用堆,也可能增加GC的负担,所以传递指针不一定是高效的。

这篇关于Go —— 逃逸分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/817807

相关文章

[职场] 公务员的利弊分析 #知识分享#经验分享#其他

公务员的利弊分析     公务员作为一种稳定的职业选择,一直备受人们的关注。然而,就像任何其他职业一样,公务员职位也有其利与弊。本文将对公务员的利弊进行分析,帮助读者更好地了解这一职业的特点。 利: 1. 稳定的职业:公务员职位通常具有较高的稳定性,一旦进入公务员队伍,往往可以享受到稳定的工作环境和薪资待遇。这对于那些追求稳定的人来说,是一个很大的优势。 2. 薪资福利优厚:公务员的薪资和

高度内卷下,企业如何通过VOC(客户之声)做好竞争分析?

VOC,即客户之声,是一种通过收集和分析客户反馈、需求和期望,来洞察市场趋势和竞争对手动态的方法。在高度内卷的市场环境下,VOC不仅能够帮助企业了解客户的真实需求,还能为企业提供宝贵的竞争情报,助力企业在竞争中占据有利地位。 那么,企业该如何通过VOC(客户之声)做好竞争分析呢?深圳天行健企业管理咨询公司解析如下: 首先,要建立完善的VOC收集机制。这包括通过线上渠道(如社交媒体、官网留言

打包体积分析和优化

webpack分析工具:webpack-bundle-analyzer 1. 通过<script src="./vue.js"></script>方式引入vue、vuex、vue-router等包(CDN) // webpack.config.jsif(process.env.NODE_ENV==='production') {module.exports = {devtool: 'none

Java中的大数据处理与分析架构

Java中的大数据处理与分析架构 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们来讨论Java中的大数据处理与分析架构。随着大数据时代的到来,海量数据的存储、处理和分析变得至关重要。Java作为一门广泛使用的编程语言,在大数据领域有着广泛的应用。本文将介绍Java在大数据处理和分析中的关键技术和架构设计。 大数据处理与

段,页,段页,三种内存(RAM)管理机制分析

段,页,段页         是为实现虚拟内存而产生的技术。直接使用物理内存弊端:地址空间不隔离,内存使用效率低。 段 段:就是按照二进制文件的格式,在内存给进程分段(包括堆栈、数据段、代码段)。通过段寄存器中的段表来进行虚拟地址和物理地址的转换。 段实现的虚拟地址 = 段号+offset 物理地址:被分为很多个有编号的段,每个进程的虚拟地址都有段号,这样可以实现虚实地址之间的转换。其实所谓的地

mediasoup 源码分析 (八)分析PlainTransport

mediasoup 源码分析 (六)分析PlainTransport 一、接收裸RTP流二、mediasoup 中udp建立过程 tips 一、接收裸RTP流 PlainTransport 可以接收裸RTP流,也可以接收AES加密的RTP流。源码中提供了一个通过ffmpeg发送裸RTP流到mediasoup的脚本,具体地址为:mediasoup-demo/broadcaste

Java并发编程—阻塞队列源码分析

在前面几篇文章中,我们讨论了同步容器(Hashtable、Vector),也讨论了并发容器(ConcurrentHashMap、CopyOnWriteArrayList),这些工具都为我们编写多线程程序提供了很大的方便。今天我们来讨论另外一类容器:阻塞队列。   在前面我们接触的队列都是非阻塞队列,比如PriorityQueue、LinkedList(LinkedList是双向链表,它实现了D

线程池ThreadPoolExecutor类源码分析

Java并发编程:线程池的使用   在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:   如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。   那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?

ConcurrentHashMap之源码分析

集合是编程中最常用的数据结构。而谈到并发,几乎总是离不开集合这类高级数据结构的支持。比如两个线程需要同时访问一个中间临界区(Queue),比如常会用缓存作为外部文件的副本(HashMap)。这篇文章主要分析jdk1.5的3种并发集合类型(concurrent,copyonright,queue)中的ConcurrentHashMap,让我们从原理上细致的了解它们,能够让我们在深度项目开发中获益非浅

Hashtable的源码分析

Hashtable简介     Hashtable同样是基于哈希表实现的,同样每个元素是一个key-value对,其内部也是通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。     Hashtable也是JDK1.0引入的类,是线程安全的,能用于多线程环境中。     Hashtable同样实现了Serializable接口,它支持序列化,实现了Cloneable接