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

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.mzph.cn/news/881702.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Qt QTableWidget多行表头、表头折行显示

表头折行显示 //方法一QVector<QString> chNames;chNames<<"表头1"<<"表头2长命名abcdefg";ui.tableWidget->setColumnCount(chNames.size()1);ui.tableWidget->setHorizontalHeaderItem(0, new QTableWidgetItem(QString::fromL…

【C】C语言常见概念~

C语言常见概念 转义字符 转义字符&#xff0c;顾名思义&#xff0c;转变原来意思的字符 比如 #include <stdio.h> int main() {printf("abcndef");return 0; }输出的结果为&#xff1a; 将代码修改一下&#xff1a; #include <stdio.h> int main(…

双目视觉搭配YOLO实现3D测量

一、简介 双目&#xff08;Stereo Vision&#xff09;技术是一种利用两个相机来模拟人眼视觉的技术。通过对两个相机获取到的图像进行分析和匹配&#xff0c;可以计算出物体的深度信息。双目技术可以实现物体的三维重建、距离测量、运动分析等应用。 双目技术的原理是通过两…

SpringBoot基础(五):集成JUnit5

SpringBoot基础系列文章 SpringBoot基础(一)&#xff1a;快速入门 SpringBoot基础(二)&#xff1a;配置文件详解 SpringBoot基础(三)&#xff1a;Logback日志 SpringBoot基础(四)&#xff1a;bean的多种加载方式 SpringBoot基础(五)&#xff1a;集成JUnit5 目录 一、JUnit…

敏捷开发 与 Scrum

‌敏捷开发的概念起源于20世纪90年代末期&#xff0c;旨在解决传统软件开发方法中存在的效率低下问题。‌ 传统软件开发方法因其繁琐的过程和对文档的严格要求&#xff0c;导致了“重型化危机”&#xff0c;这使得开发效率大幅下降。为了应对这些问题&#xff0c;敏捷方法应运…

微信小程序 - 01 - 一些补充和注意点(补充ing...)

目录 一、节流二、在一个发请求的函数中&#xff0c;只有发生下拉动作&#xff0c;才执行关闭下拉代码 最近在学微信小程序&#xff0c;把学习过程中的一些补充和注意点总结一下&#xff0c;内容会比较简单&#xff0c;因为只涉及基础知识&#xff0c;供个人参考 一、节流 情…

AIGC毕设项目分享:基于RAG的数字人对话系统及其应用

本研究的主要目标是设计并实现一个基于检索增强生成&#xff08;RAG&#xff09;技术的数字人对话系统&#xff0c;旨在提升数字人系统在多轮对话中的上下文管理、情境感知能力以及动态内容生成效果。系统结合了深度学习中的最新大语言模型技术&#xff0c;通过引入RAG框架来增…

K8S配置MySQL主从自动水平扩展

前提环境 操作系统Ubuntu 22.04 K8S 1.28.2集群&#xff08;1个master2个node&#xff09; MySQL 5.7.44部署在K8S的主从集群 metrics-server v0.6.4 概念简介 在K8s中扩缩容分为两种 ●Node层面&#xff1a;对K8s物理节点扩容和缩容&#xff0c;根据业务规模实现物理节点自动扩…

Python列表专题:list与in

Python是一种强大的编程语言,其中列表(list)是最常用的数据结构之一。列表允许我们存储多个元素,并且可以方便地进行各种操作。在Python中,in运算符被广泛用于检测元素是否存在于列表中。本文将深入探讨Python列表及其与in运算符的结合使用。 1. Python列表的基础 1.1 什…

爬虫案例——网易新闻数据的爬取

案例需求&#xff1a; 1.爬取该新闻网站——&#xff08;网易新闻&#xff09;的数据&#xff0c;包括标题和链接 2.爬取所有数据&#xff08;翻页参数&#xff09; 3.利用jsonpath解析数据 分析&#xff1a; 该网站属于异步加载网站——直接网页中拿不到&#xff0c;需要…

项目开发--基于docker实现模型容器化服务

背景 1、docker-compose build 和 docker-compose up -d分别是什么作用&#xff1f; 2、如何进入新构建的容器当中 3、模型保存的方法区别 4、如何让docker容器启动的时候能使用cuda进行模型推理加速 5、如何实现容器的迭代 解决方案 问题1 docker-compose build 和 docker…

WindowsAPI|每天了解几个winAPI接口之Iphlpapi.h网络配置相关文档详细分析三

上一篇&#xff1a;WindowsAPI|每天了解几个winAPI接口之Iphlpapi.h网络配置相关文档详细分析二 如果有错误欢迎指正批评&#xff0c;在此只作为科普和参考。 文章目录 GetTcpTable&GetExtendedTcpTable&#xff1a;获取TCP连接的信息GetOwnerModuleFromTcpEntry&#xff1…

MySQL-08.DDL-表结构操作-创建-案例

一.MySQL创建表的方式 1.首先根据需求文档定义出原型字段&#xff0c;即从需求文档中可以直接设计出来的字段 2.再在原型字段的基础上加上一些基础字段&#xff0c;构成整个表结构的设计 我们采用基于图形化界面的方式来创建表结构 二.案例 原型字段 各字段设计如下&…

深入理解线性表--顺序表

目录 顺序表- Seqlist -> sequence 顺序 list 表 顺序表的概念 问题与解答 顺序表的分类 静态顺序表 动态顺序表 问题与解答(递进式) 动态顺序表的实现 尾插 头插 尾删 头删 指定位置插入 指定位置删除 销毁 总结 前言&#xff1a;线性表是具有相同特性的一类数据结构…

2024 年 04 月编程语言排行榜,PHP 排名创新低?

编程语言的流行度总是变化莫测&#xff0c;每个月的排行榜都揭示着新的趋势。2024年4月的编程语言排行榜揭示了一个引人关注的现象&#xff1a;PHP的排名再次下滑&#xff0c;创下了历史新低。这种变化对于PHP开发者和整个技术社区来说&#xff0c;意味着什么呢&#xff1f; P…

现代数字信号处理I-P3 MVUE学习笔记

目录 1. 参数估计问题的提出与本质 2. 估计的性质 2.1 Ancillary&#xff08;多余估计&#xff09; 例1&#xff0c;Ancillary估计量 2. Uniformly Optimal 3. Sufficiency充分性 3.1 统计量充分性定义 例2&#xff1a;利用充分统计量定义获取伯努利分布的充分统计量 …

服务器源IP暴露后的安全风险及防御措施

在互联网安全领域&#xff0c;服务器的源IP地址泄露可能成为黑客攻击的切入点。本文将列举十种常见的攻击类型&#xff0c;并提供相应的防御建议&#xff0c;帮助管理员们更好地保护服务器免受潜在威胁。 一、引言 服务器源IP地址的暴露意味着攻击者可以直接针对服务器发起攻击…

Anaroute - 理论学习(一)

一、贡献&#xff1a; 框架能够在考虑特定约束的同时&#xff0c;高效地完成复杂AMS设计的布线&#xff0c;并实现签署质量的性能。 提出了一种对称性约束的分配算法&#xff0c;根据引脚位置分配合适的网络匹配要求新的引脚聚类策略&#xff0c;以实现规律性的布线模式&…

Flink学习地址

--基础概念 概览 | Apache Flink --应用系列 如何学习Flink&#xff1a;糙快猛的大数据之路&#xff08;图文并茂&#xff09;_flink 学习-CSDN博客 --Python系列 pyflink实时接收kafka数据至hive_pyflink下kafka数据经过处理后插入hive-CSDN博客 Pyflink教程(一)&#…

UniApp适合哪些场景

UniApp作为一款跨平台的移动应用开发框架&#xff0c;因其高效、灵活和强大的特性&#xff0c;适用于多种应用场景。以下是一些UniApp适用的主要场景&#xff1a; 多平台应用开发&#xff1a; 对于需要同时在iOS、Android、Web以及各种小程序平台发布的应用&#xff0c;UniApp可…