Golang 逃逸分析(Escape Analysis)理解与实践篇

devtools/2024/10/18 5:04:35/

Golang 逃逸分析(Escape Analysis)理解与实践篇

文章目录

      • 1.逃逸分析
      • 2.相关知识(栈、堆、GC分析)
      • 3.逃逸分析综合-实践 demo

逃逸分析(Escape Analysis)是编译器在编译期进行的一项优化技术,是Glang非常重要的性能优化工具。其目的是判断某个变量是否会被函数外部引用,或者超出其作用范围。

1.逃逸分析

如果变量仅在函数内部使用,那么它可以安全地分配在栈上;如果变量“逃逸”到函数外部(例如返回给调用者或者传递给其他协程),编译器会将其分配到堆上,以保证其生命周期不会在栈帧结束时被销毁。

1.返回指针:如果函数返回了局部变量的指针,该变量就会逃逸到堆上。
2.闭包捕获变量:闭包函数中捕获的外部变量也会导致变量逃逸。
3.接口类型的转换:接口转换时,如果具体类型需要被持久化存储,那么它可能逃逸。
4.动态分配的内存:例如使用 new 或者 make 创建的对象,编译器可能会决定将它们分配在堆上。
5.使用 Goroutine :需要特别注意变量逃逸问题。因为 Goroutine 会并发执行,某些变量可能在 Goroutine 中被引用,导致它们逃逸到堆上。

Golang 提供了逃逸分析的工具(编译时查看函数中哪些变量发生了逃逸):

go build -gcflags="-m"

2.相关知识(栈、堆、GC分析)

栈分配:栈是 Go 中快速分配和释放内存的区域。栈上的变量在函数返回时自动销毁,不需要额外的垃圾回收(GC)开销。
堆分配:堆上的内存分配速度相对较慢,且需要依赖 Go 的垃圾回收机制进行管理。频繁的堆分配会导致 GC 的频率增加,从而影响性能。
GC:Go 的垃圾回收器是三色标记清除算法,每次垃圾回收会对堆上的所有对象进行追踪和标记,回收不再使用的内存。

  • 白色(待回收):白色的对象表示未被访问到的对象。在垃圾回收开始时,所有的对象最初都被标记为白色。最终,所有仍然是白色的对象将被认定为不可达的,并在清除阶段被回收。
  • 灰色(待处理):灰色的对象表示已经被垃圾回收器访问到,但其引用的对象还没有完全处理。灰色对象需要进一步追踪其引用的对象。
  • 黑色(已处理):黑色的对象表示已经被处理过,它的引用对象也已经被追踪,不会被再次检查。黑色对象是安全的,表示它们依然在使用,不会被回收。

启用 GC 配置:

export GOGC=50 # 设置 GOGC 为 50,增加 GC 频率,降低内存占用
export GODEBUG=gctrace=1  # GC 运行的详细信息,包括 GC 触发的时机、暂停时间、以及每次回收时清理的内存量

GC 测试 demo:

package mainimport ("fmt""runtime""time"
)func main() {// 启动一个 Goroutine,持续分配内存,触发 GCgo func() {for {_ = make([]byte, 10<<20) // 每次分配 10MB 内存time.Sleep(100 * time.Millisecond)}}()// 打印内存使用情况和 GC 次数var m runtime.MemStatsfor i := 0; i < 10; i++ {runtime.ReadMemStats(&m)fmt.Printf("Alloc = %v MiB, Sys = %v MiB, NumGC = %v\n", m.Alloc/1024/1024, m.Sys/1024/1024, m.NumGC)time.Sleep(1 * time.Second)}
}

3.逃逸分析综合-实践 demo

package mainimport ("fmt""runtime"
)// 示例1:返回局部变量的指针
func escapeToHeap() *int {a := 42return &a // 逃逸到堆上,因为返回了局部变量的指针
}// 示例2:闭包捕获外部变量
func closureExample() func() int {x := 100return func() int {return x // x 逃逸到堆上,闭包捕获了外部变量}
}// 示例3:接口转换导致逃逸
func interfaceExample() {var i interface{}i = 42  // 逃逸到堆上,因为 interface 可能会持有较大对象fmt.Println(i)
}// 示例4:动态分配内存
func dynamicAllocation() {p := new(int) // 逃逸到堆上,使用 new 分配内存*p = 42fmt.Println(*p)
}// 示例5:在栈上分配
func noEscape() {x := 42 // 没有逃逸,x 在栈上分配fmt.Println(x)
}// 示例6:Goroutine 中的逃逸分析
func goroutineEscape() {x := 42go func() {fmt.Println(x) // x 逃逸到堆上,因为被 Goroutine 使用}()
}func main() {// 打印当前内存使用情况var m runtime.MemStatsruntime.ReadMemStats(&m)fmt.Printf("Initial Alloc = %v KB\n", m.Alloc/1024)// 测试逃逸分析的各个示例fmt.Println("Running escapeToHeap()")escapeToHeap()fmt.Println("Running closureExample()")closure := closureExample()fmt.Println(closure())fmt.Println("Running interfaceExample()")interfaceExample()fmt.Println("Running dynamicAllocation()")dynamicAllocation()fmt.Println("Running noEscape()")noEscape()fmt.Println("Running goroutineEscape()")goroutineEscape()// 打印最终内存使用情况runtime.ReadMemStats(&m)fmt.Printf("Final Alloc = %v KB\n", m.Alloc/1024)
}

编译-逃逸分析

[jn@jn ~]$ go build -gcflags="-m" escape.go
# command-line-arguments
./escape.go:9:6: can inline escapeToHeap
./escape.go:15:6: can inline closureExample
./escape.go:17:9: can inline closureExample.func1
./escape.go:26:13: inlining call to fmt.Println
./escape.go:33:13: inlining call to fmt.Println
./escape.go:39:13: inlining call to fmt.Println
./escape.go:45:5: can inline goroutineEscape.func1
./escape.go:46:14: inlining call to fmt.Println
./escape.go:54:12: inlining call to fmt.Printf
./escape.go:57:13: inlining call to fmt.Println
./escape.go:58:14: inlining call to escapeToHeap
./escape.go:60:13: inlining call to fmt.Println
./escape.go:61:27: inlining call to closureExample
./escape.go:17:9: can inline main.func1
./escape.go:62:21: inlining call to main.func1
./escape.go:62:13: inlining call to fmt.Println
./escape.go:64:13: inlining call to fmt.Println
./escape.go:67:13: inlining call to fmt.Println
./escape.go:70:13: inlining call to fmt.Println
./escape.go:73:13: inlining call to fmt.Println
./escape.go:78:12: inlining call to fmt.Printf
./escape.go:10:2: moved to heap: a
./escape.go:17:9: func literal escapes to heap
./escape.go:25:2: 42 escapes to heap
./escape.go:26:13: ... argument does not escape
./escape.go:31:10: new(int) does not escape
./escape.go:33:13: ... argument does not escape
./escape.go:33:14: *p escapes to heap
./escape.go:39:13: ... argument does not escape
./escape.go:39:13: x escapes to heap
./escape.go:45:5: func literal escapes to heap
./escape.go:46:14: ... argument does not escape
./escape.go:46:14: x escapes to heap
./escape.go:54:12: ... argument does not escape
./escape.go:54:47: m.Alloc / 1024 escapes to heap
./escape.go:57:13: ... argument does not escape
./escape.go:57:14: "Running escapeToHeap()" escapes to heap
./escape.go:60:13: ... argument does not escape
./escape.go:60:14: "Running closureExample()" escapes to heap
./escape.go:61:27: func literal does not escape
./escape.go:62:13: ... argument does not escape
./escape.go:62:21: ~R0 escapes to heap
./escape.go:64:13: ... argument does not escape
./escape.go:64:14: "Running interfaceExample()" escapes to heap
./escape.go:67:13: ... argument does not escape
./escape.go:67:14: "Running dynamicAllocation()" escapes to heap
./escape.go:70:13: ... argument does not escape
./escape.go:70:14: "Running noEscape()" escapes to heap
./escape.go:73:13: ... argument does not escape
./escape.go:73:14: "Running goroutineEscape()" escapes to heap
./escape.go:78:12: ... argument does not escape
./escape.go:78:45: m.Alloc / 1024 escapes to heap
[jn@jn ~]$

run

[jn@jn ~]$ ./escape
Initial Alloc = 187 KB
Running escapeToHeap()
Running closureExample()
100
Running interfaceExample()
42
Running dynamicAllocation()
42
Running noEscape()
42
Running goroutineEscape()
Final Alloc = 190 KB
[jn@jn ~]$

end

1.尽量避免将局部变量的指针返回给外部。
2.使用闭包时注意外部变量的捕获,避免逃逸。
3.尽量减少接口类型和 Goroutine 导致的逃逸。

http://www.ppmy.cn/devtools/126650.html

相关文章

HTML 标签简写及全称

HTML 标签简写及全称 HTML(HyperText Markup Language)是构建网页的标准标记语言。它由一系列标签组成,这些标签通常成对出现,如 <html> 和 </html>,用于定义网页的结构和内容。HTML标签可以包含简写形式,这些简写形式在功能上与全称标签等价,但书写更为简…

互动式教育技术:Spring Boot师生共评作业管理系统

3系统分析 3.1可行性分析 通过对本师生共评的作业管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本师生共评的作业管理系统采用JAVA作为开发语言&…

No.17 笔记 | XXE漏洞:XML外部实体注入攻击

1. XXE漏洞概览 XXE&#xff08;XML External Entity&#xff09;是一种允许攻击者干扰应用程序对XML输入处理的漏洞。 1.1 XXE漏洞比喻 想象XML解析器是一个听话的机器人&#xff0c;而XXE就是利用这个机器人的"过分听话"来获取不应该获取的信息。 1.2 XXE漏洞危…

蓄电池在线监测:保障电力安全的智能之选---安科瑞 吴雅芳

一、蓄电池在线监测的重要性 随着科技的飞速发展&#xff0c;蓄电池在各个领域的应用日益广泛&#xff0c;从通信、电力到金融、医疗等行业&#xff0c;蓄电池都扮演着至关重要的角色。然而&#xff0c;蓄电池在使用过程中也面临着诸多问题。 蓄电池老化可能导致鼓胀、短路、漏…

Python 语言学习——做题记录 2.3

这次主要练习集合这一数据类型。 P1. 洛谷B3633集合运算2 import sys n1input() a1sys.stdin.readline() a2a1.split() A{int(i) for i in a2} #print(A) n2input() b1sys.stdin.readline() b2b1.split() B{int(i) for i in b2} #print(B)print(len(A)) CA&B DA|B Uset(ra…

【读书笔记-《30天自制操作系统》-30】Day31

《30天自制操作系统》这部书的内容完结了&#xff0c;接下来是本书作者的后记部分和自己学习的一些体会。 在本书作者看来&#xff0c;这个操作系统其实还有诸多值得完善改进的地方。 首先是可以增加的功能。 比如当前的操作系统还不支持向磁盘写入&#xff0c;开发过程中完…

高可用架构:负载均衡和集群管理

随着互联网技术的飞速发展&#xff0c;越来越多的应用需要处理大量用户请求。为了确保系统的稳定性和高可用性&#xff0c;负载均衡和集群管理成为了关键技术。本文将深入探讨这两者的原理、实现方式以及在实际运维中的应用&#xff0c;旨在为广大技术人员提供一份详细且通俗易…

《计算机视觉》—— 疲劳检测

文章目录 一、疲劳检测实现的思想二、代码实现 一、疲劳检测实现的思想 了解以下几篇文章有助于了解疲劳检测的方法 基于dlib库的人脸检测 https://blog.csdn.net/weixin_73504499/article/details/142977202?spm1001.2014.3001.5501 基于dlib库的人脸关键点定位 https://blo…