golang 逃逸分析

本贴最后更新于 1475 天前,其中的信息可能已经天翻地覆

golang 逃逸分析

垃圾回收是 Go - 自动内存管理的一个便利功能, 使代码更清洁,内存泄漏的可能性更小。但是,GC 还会增加间接性能消耗,因为程序需要定期停止并收集未使用的对象。Go 编译器足够智能,可以自动决定是否应在堆上分配变量,之后需要在堆上收集垃圾,或者是否可以将其分配为该变量的函数的栈的一部分。栈与堆分配变量不同,堆分配变量不会产生任何 GC 开销,因为它们在栈的其余部分(当功能返回时)被销毁。

例如,Go 的逃生分析比 HotSpot JVM 更基本。基本规则是,如果从申报的函数返回对变量的引用,则会"逃逸" - 函数返回后可以引用该变量,因此必须将其堆分配。这是比较复杂的,因为:

  • 调用其他功能的函数
  • 分配给结构体成员的引用
  • 切片和 maps
  • cgo 将指针指向变量

为了执行逃生分析,Go 在编译时构建一个函数调用图,并跟踪输入参数和返回值的流。函数可能引用其中一个参数,但如果该引用未返回,变量不会逃逸。函数也可以返回引用,但在申明变量返回的函数之前,该引用可能由栈中的另一个函数取消引用或未返回。为了说明一些简单的案例,我们可以运行编译器,这将打印详细的逃生分析信息:-gcflags '-m'

package main type S struct {} func main() { var x S _ = identity(x) } func identity(x S) S { return x }

你必须用 go run -gcflags '-m -l' '-l'标签阻止功能被内联 (这是另一个时间的主题) 来构建这个功能。输出是:什么都没有!Go 使用值传递,因此始终将变量复制到栈中。在没有引用的一般代码中,总是很少使用栈分配。没有逃生分析可做。再看下面一个例子:

package main type S struct {} func main() { var x S y := &x _ = *identity(y) } func identity(z *S) *S { return z }

输出:

$ go run -gcflags '-m -l' main.go # command-line-arguments .\main.go:11:15: leaking param: z to result ~r1 level=0

第一行显示变量"流过":输入变量返回为输出。但不采取参考,所以变量不会逃逸。不在 main 返回之后没有对 x 的引用存在,因此 x 分配在 main 的堆上。 第三个实验:

package main type S struct {} func main() { var x S _ = *ref(x) } func ref(z S) *S { return &z }

输出:

$ go run -gcflags '-m -l' main.go # command-line-arguments .\main.go:10:10: moved to heap: z

现在有一些逃避正在发生。请记住,go 是值传递,所以 z 是 main 中 x 变量的副本。 返回 z 的引用,所以 z 不能是栈的一部分-返回时的参考点在哪里?取而代之的是它逃到堆。尽管 Go 在不取消计算参考值的情况下会立即扔掉引用,但 Go 的逃逸分析不够精密,无法找出这一点 - 它只查看输入和返回变量的流。值得注意的是,在这种情况下,如果我们不阻止它,编译器就会强调这一点。

如果将引用分配给结构成员,该怎么办?

package main type S struct { M *int } func main() { var i int refStruct(i) } func refStruct(y int) (z S) { z.M = &y return z }

输出:

$ go run -gcflags '-m -l' main.go # command-line-arguments .\main.go:13:16: moved to heap: y

在这种情况下,Go 仍然可以跟踪引用流,即使引用是结构体的成员。既然 refStruct 做了引用并返回它,y 就必须逃逸。与本案例相比:

package main type S struct { M *int } func main() { var i int refStruct(&i) } func refStruct(y *int) (z S) { z.M = y return z }

输出:

$ go run -gcflags '-m -l' main.go # command-line-arguments .\main.go:13:16: leaking param: y to result z level=0

由于 main 做了引用并传递 refStruct,引用永远不会超过申报引用变量的栈。这和前面的程序有稍微不同的语义,但如果第二个程序足够的话,它会更有效率:在第一个例子 i 必须分配在 main 的栈上,然后在堆上重新分配并将其复制为 refStruct 的参数。在第二个示例中 i 只分配一次,并传递引用。

一个更深入的例子:

package main type S struct { M *int } func main() { var x S var i int ref(&i, &x) } func ref(y *int, z *S) { z.M = y }

输出:

$ go run -gcflags '-m -l' main.go # command-line-arguments .\main.go:14:10: leaking param: y .\main.go:14:18: z does not escape .\main.go:10:6: moved to heap: i

这里的问题是 y 是分配给输入结构体的成员。Go 无法跟踪该关系 - 输入仅允许流到输出 - 因此逃逸分析失败,必须对变量进行堆分配。有许多有据可查的案例(as of Go 1.5),由于 go 逃逸分析的限制,必须堆分配变量 -请参阅此链接

最后,maps 和切片呢?请记住,maps 和切片实际上只是使用指针构建到堆分配的内存:切片结构暴露在包中(SliceHeader )中。map 结构是更难找到的,但它存在:hmap 。如果这些结构无法逃逸,它们将被栈分配,但备份数组或哈希存储桶中的数据本身将每次都堆分配。避免这种情况的唯一方法是分配一个固定大小的数组(如[10000]int)。

如果您已经看过分析程序的堆使用情况 ,并且需要减少 GC 时间,则可能会从堆中移动频繁分配的变量而获得一些收获。这也只是一个引人入胜的话题:要进一步阅读 HotSpot JVM 如何处理逃逸分析,请查看这篇文章 ,其中涉及堆栈分配,以及检测何时可以消除同步。

  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    498 引用 • 1395 回帖 • 248 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • AWS
    11 引用 • 28 回帖 • 11 关注
  • 资讯

    资讯是用户因为及时地获得它并利用它而能够在相对短的时间内给自己带来价值的信息,资讯有时效性和地域性。

    56 引用 • 85 回帖 • 2 关注
  • Netty

    Netty 是一个基于 NIO 的客户端-服务器编程框架,使用 Netty 可以让你快速、简单地开发出一个可维护、高性能的网络应用,例如实现了某种协议的客户、服务端应用。

    49 引用 • 33 回帖 • 35 关注
  • BAE

    百度应用引擎(Baidu App Engine)提供了 PHP、Java、Python 的执行环境,以及云存储、消息服务、云数据库等全面的云服务。它可以让开发者实现自动地部署和管理应用,并且提供动态扩容和负载均衡的运行环境,让开发者不用考虑高成本的运维工作,只需专注于业务逻辑,大大降低了开发者学习和迁移的成本。

    19 引用 • 75 回帖 • 668 关注
  • Markdown

    Markdown 是一种轻量级标记语言,用户可使用纯文本编辑器来排版文档,最终通过 Markdown 引擎将文档转换为所需格式(比如 HTML、PDF 等)。

    170 引用 • 1529 回帖
  • Shell

    Shell 脚本与 Windows/Dos 下的批处理相似,也就是用各类命令预先放入到一个文件中,方便一次性执行的一个程序文件,主要是方便管理员进行设置或者管理用的。但是它比 Windows 下的批处理更强大,比用其他编程程序编辑的程序效率更高,因为它使用了 Linux/Unix 下的命令。

    124 引用 • 74 回帖 • 1 关注
  • 爬虫

    网络爬虫(Spider、Crawler),是一种按照一定的规则,自动地抓取万维网信息的程序。

    106 引用 • 275 回帖
  • Linux

    Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX 和 Unix 的多用户、多任务、支持多线程和多 CPU 的操作系统。它能运行主要的 Unix 工具软件、应用程序和网络协议,并支持 32 位和 64 位硬件。Linux 继承了 Unix 以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。

    952 引用 • 944 回帖 • 1 关注
  • 服务器

    服务器,也称伺服器,是提供计算服务的设备。由于服务器需要响应服务请求,并进行处理,因此一般来说服务器应具备承担服务并且保障服务的能力。

    125 引用 • 585 回帖
  • 小薇

    小薇是一个用 Java 写的 QQ 聊天机器人 Web 服务,可以用于社群互动。

    由于 Smart QQ 从 2019 年 1 月 1 日起停止服务,所以该项目也已经停止维护了!

    35 引用 • 468 回帖 • 762 关注
  • Logseq

    Logseq 是一个隐私优先、开源的知识库工具。

    Logseq is a joyful, open-source outliner that works on top of local plain-text Markdown and Org-mode files. Use it to write, organize and share your thoughts, keep your to-do list, and build your own digital garden.

    7 引用 • 69 回帖
  • Wide

    Wide 是一款基于 Web 的 Go 语言 IDE。通过浏览器就可以进行 Go 开发,并有代码自动完成、查看表达式、编译反馈、Lint、实时结果输出等功能。

    欢迎访问我们运维的实例: https://wide.b3log.org

    30 引用 • 218 回帖 • 639 关注
  • Ubuntu

    Ubuntu(友帮拓、优般图、乌班图)是一个以桌面应用为主的 Linux 操作系统,其名称来自非洲南部祖鲁语或豪萨语的“ubuntu”一词,意思是“人性”、“我的存在是因为大家的存在”,是非洲传统的一种价值观,类似华人社会的“仁爱”思想。Ubuntu 的目标在于为一般用户提供一个最新的、同时又相当稳定的主要由自由软件构建而成的操作系统。

    127 引用 • 169 回帖
  • wolai

    我来 wolai:不仅仅是未来的云端笔记!

    2 引用 • 14 回帖
  • 叶归
    6 引用 • 17 回帖 • 14 关注
  • Kubernetes

    Kubernetes 是 Google 开源的一个容器编排引擎,它支持自动化部署、大规模可伸缩、应用容器化管理。

    116 引用 • 54 回帖
  • 学习

    “梦想从学习开始,事业从实践起步” —— 习近平

    173 引用 • 518 回帖
  • 服务

    提供一个服务绝不仅仅是简单的把硬件和软件累加在一起,它包括了服务的可靠性、服务的标准化、以及对服务的监控、维护、技术支持等。

    41 引用 • 24 回帖 • 2 关注
  • gRpc
    11 引用 • 9 回帖 • 92 关注
  • 工具

    子曰:“工欲善其事,必先利其器。”

    298 引用 • 763 回帖
  • 安全

    安全永远都不是一个小问题。

    203 引用 • 818 回帖 • 2 关注
  • SSL

    SSL(Secure Sockets Layer 安全套接层),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议。TLS 与 SSL 在传输层对网络连接进行加密。

    70 引用 • 193 回帖 • 409 关注
  • ReactiveX

    ReactiveX 是一个专注于异步编程与控制可观察数据(或者事件)流的 API。它组合了观察者模式,迭代器模式和函数式编程的优秀思想。

    1 引用 • 2 回帖 • 182 关注
  • 心情

    心是产生任何想法的源泉,心本体会陷入到对自己本体不能理解的状态中,因为心能产生任何想法,不能分出对错,不能分出自己。

    59 引用 • 369 回帖
  • JVM

    JVM(Java Virtual Machine)Java 虚拟机是一个微型操作系统,有自己的硬件构架体系,还有相应的指令系统。能够识别 Java 独特的 .class 文件(字节码),能够将这些文件中的信息读取出来,使得 Java 程序只需要生成 Java 虚拟机上的字节码后就能在不同操作系统平台上进行运行。

    180 引用 • 120 回帖
  • FreeMarker

    FreeMarker 是一款好用且功能强大的 Java 模版引擎。

    23 引用 • 20 回帖 • 463 关注
  • Follow
    4 引用 • 12 回帖 • 13 关注