您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > Go语言

Go 语言机制之逃逸分析

时间:2019-05-30 10:50:33  来源:  作者:
「GCTT 出品」Go 语言机制之逃逸分析

 

前序(Prelude)

本系列文章总共四篇,主要帮助大家理解 Go 语言中一些语法结构和其背后的设计原则,包括指针、栈、堆、逃逸分析和值/指针传递。这是第二篇,主要介绍堆和逃逸分析。

以下是本系列文章的索引:

  1. 「GCTT 出品」Go 语言机制之栈和指针
  2. Go 语言机制之逃逸分析
  3. Go 语言机制之内存剖析
  4. Go 语言机制之数据和语法的设计哲学

介绍(Introduction)

在四部分系列的第一部分,我用一个将值共享给 goroutine 栈的例子介绍了指针结构的基础。而我没有说的是值存在栈之上的情况。为了理解这个,你需要学习值存储的另外一个位置:堆。有这个基础,就可以开始学习逃逸分析。

逃逸分析是编译器用来决定你的程序中值的位置的过程。特别地,编译器执行静态代码分析,以确定一个构造体的实例化值是否会逃逸到堆。在 Go 语言中,你没有可用的关键字或者函数,能够直接让编译器做这个决定。只能够通过你写代码的方式来作出这个决定。

堆(Heaps)

堆是内存的第二区域,除了栈之外,用来存储值的地方。堆无法像栈一样能自清理,所以使用这部分内存会造成很大的开销(相比于使用栈)。重要的是,开销跟 GC(垃圾收集),即被牵扯进来保证这部分区域干净的程序,有很大的关系。当垃圾收集程序运行时,它会占用你的可用 CPU 容量的 25%。更有甚者,它会造成微秒级的 “stop the world” 的延时。拥有 GC 的好处是你可以不再关注堆内存的管理,这部分很复杂,是历史上容易出错的地方。

在 Go 中,会将一部分值分配到堆上。这些分配给 GC 带来了压力,因为堆上没有被指针索引的值都需要被删除。越多需要被检查和删除的值,会给每次运行 GC 时带来越多的工作。所以,分配算法不断地工作,以平衡堆的大小和它运行的速度。

共享栈(Sharing Stacks)

在 Go 语言中,不允许 goroutine 中的指针指向另外一个 goroutine 的栈。这是因为当栈增长或者收缩时,goroutine 中的栈内存会被一块新的内存替换。如果运行时需要追踪指针指向其他的 goroutine 的栈,就会造成非常多需要管理的内存,以至于更新指向那些栈的指针将使 “stop the world” 问题更严重。

这里有一个栈被替换好几次的例子。看输出的第 2 和第 6 行。你会看到 main 函数中的栈的字符串地址值改变了两次。https://play.golang.org/p/pxn5u4EBSI

逃逸机制(Escape Mechanics)

任何时候,一个值被分享到函数栈帧范围之外,它都会在堆上被重新分配。这是逃逸分析算法发现这些情况和管控这一层的工作。(内存的)完整性在于确保对任何值的访问始终是准确、一致和高效的。

通过查看这个语言机制了解逃逸分析。https://play.golang.org/p/Y_VZxYteKO

清单 1

「GCTT 出品」Go 语言机制之逃逸分析

 

我使用 go:noinline 指令,阻止在 main 函数中,编译器使用内联代码替代函数调用。内联(优化)会使函数调用消失,并使例子复杂化。我将在下一篇博文介绍内联造成的副作用。

在表 1 中,你可以看到创建 user 值,并返回给调用者的两个不同的函数。在函数版本 1 中,返回值。

清单 2

「GCTT 出品」Go 语言机制之逃逸分析

 

我说这个函数返回的是值是因为这个被函数创建的 user 值被拷贝并传递到调用栈上。这意味着调用函数接收到的是这个值的拷贝。

你可以看下第 17 行到 20 行 user 值被构造的过程。然后在第 23 行,user 值的副本被传递到调用栈并返回给调用者。函数返回后,栈看起来如下所示。

图 1

「GCTT 出品」Go 语言机制之逃逸分析

 

你可以看到图 1 中,当调用完 createUserV1 ,一个 user 值同时存在(两个函数的)栈帧中。在函数版本 2 中,返回指针。

清单 3

「GCTT 出品」Go 语言机制之逃逸分析

 

我说这个函数返回的是指针是因为这个被函数创建的 user 值通过调用栈被共享了。这意味着调用函数接收到一个值的地址拷贝。

你可以看到在第 28 行到 31 行使用相同的字段值来构造 user 值,但在第 34 行返回时却是不同的。不是将 user 值的副本传递到调用栈,而是将 user 值的地址传递到调用栈。基于此,你也许会认为栈在调用之后是这个样子。

图 2

「GCTT 出品」Go 语言机制之逃逸分析

 

如果看到的图 2 真的发生的话,你将遇到一个问题。指针指向了栈下的无效地址空间。当 main 函数调用下一个函数,指向的内存将重新映射并将被重新初始化。

这就是逃逸分析将开始保持完整性的地方。在这种情况下,编译器将检查到,在 createUserV2 的(函数)栈中构造 user 值是不安全的,因此,替代地,会在堆中构造(相应的)值。这(个分析并处理的过程)将在第 28 行构造时立即发生。

「GCTT 出品」Go 语言机制之逃逸分析

 

可读性(Readability)

在上一篇博文中,我们知道一个函数只能直接访问它的(函数栈)空间,或者通过(函数栈空间内的)指针,通过跳转访问(函数栈空间外的)外部内存。这意味着访问逃逸到堆上的值也需要通过指针跳转。

记住 createUserV2 的代码的样子:

清单 4

「GCTT 出品」Go 语言机制之逃逸分析

 

语法隐藏了代码中真正发生的事情。第 28 行声明的变量 u 代表一个 user 类型的值。Go 代码中的类型构造不会告诉你值在内存中的位置。所以直到第 34 行返回类型时,你才知道值需要逃逸(处理)。这意味着,虽然 u 代表类型 user 的一个值,但对该值的访问必须通过指针进行。

你可以在函数调用之后,看到堆栈就像(图 3)这样。

图 3

「GCTT 出品」Go 语言机制之逃逸分析

 

在 createUserV2 函数栈中,变量 u 代表的值存在于堆中,而不是栈。这意味着用 u 访问值时,使用指针访问而不是直接访问。你可能想,为什么不让 u 成为指针,毕竟访问它代表的值需要使用指针?

清单 5

「GCTT 出品」Go 语言机制之逃逸分析

 

如果你这样做,将使你的代码缺乏重要的可读性。(让我们)离开整个函数一秒,只关注 return。

清单 6

34 return u
35 }

这个 return 告诉你什么了呢?它说明了返回 u 值的副本给调用栈。然而,当你使用 & 操作符,return 又告诉你什么了呢?

清单 7

34 return &u
35 }

多亏了 & 操作符,return 告诉你 u 被分享给调用者,因此,已经逃逸到堆中。记住,当你读代码的时候,指针是为了共享,& 操作符对应单词 "sharing"。这在提高可读性的时候非常有用,这(也)是你不想失去的部分。

清单 8

「GCTT 出品」Go 语言机制之逃逸分析

 

为了让其可以工作,你一定要通过共享指针变量(的方式)给(函数) json.Unmarshal。json.Unmarshal 调用时会创建 user 值并将其地址赋值给指针变量。https://play.golang.org/p/koI8EjpeIx

代码解释:

01:创建一个类型为 user,值为空的指针。

02:跟函数 json.Unmarshal 函数共享指针。

03:返回 u 的副本给调用者。

这里并不是很好理解,user值被 json.Unmarshal 函数创建,并被共享给调用者。

如何在构造过程中使用语法语义来改变可读性?

清单 9

「GCTT 出品」Go 语言机制之逃逸分析

 

代码解释:

01:创建一个类型为 user,值为空的变量。

02:跟函数 json.Unmarshal 函数共享 u。

03:跟调用者共享 u。

这里非常好理解。第 02 行共享 user 值到调用栈中的 json.Unmarshal,在第 03 行 user 值共享给调用者。这个共享过程将会导致 user 值逃逸。

在构建一个值时,使用值语义,并利用 & 操作符的可读性来明确值是如何被共享的。

编译器报告(Compiler Reporting)

想查看编译器(关于逃逸分析)的决定,你可以让编译器提供一份报告。你只需要在调用 go build 的时候,打开 -gcflags 开关,并带上 -m 选项。

实际上总共可以使用 4 个 -m,(但)超过 2 个级别的信息就已经太多了。我将使用 2 个 -m 的级别。

清单 10

「GCTT 出品」Go 语言机制之逃逸分析

 

你可以看到编译器报告是否需要逃逸处理的决定。编译器都说了什么呢?请再看一下引用的 createUserV1 和 createUserV2 函数。

清单 13

「GCTT 出品」Go 语言机制之逃逸分析

 

从报告中的这一行开始。

清单 14

./main.go:22: createUserV1 &u does not escape

这是说在函数 createUserV1 调用 println 不会造成 user 值逃逸到堆。这是必须检查的,因为它将会跟函数 println 共享(u)。

接下来看报告中的这几行。

清单 15

「GCTT 出品」Go 语言机制之逃逸分析

 

这几行是说,类型为 user,并在第 31 行被赋值的 u 的值,因为第 34 行的 return 逃逸。最后一行是说,跟之前一样,在 33 行调用 println 不会造成 user 值逃逸。

阅读这些报告可能让人感到困惑,(编译器)会根据所讨论的变量的类型是基于值类型还是指针类型而略有变化。

将 u 改为指针类型的 *user,而不是之前的命名类型 user。

清单 16

「GCTT 出品」Go 语言机制之逃逸分析

 

再次生成报告。

清单 17

「GCTT 出品」Go 语言机制之逃逸分析

 

现在报告说在 28 行赋值的指针类型 *user,u 引用的 user 值,因为 34 行的 return 逃逸。

结论

值在构建时并不能决定它将存在于哪里。只有当一个值被共享,编译器才能决定如何处理这个值。当你在调用时,共享了栈上的一个值时,它就会逃逸。在下一篇中你将探索一个值逃逸的其他原因。

这些文章试图引导你选择给定类型的值或指针的指导原则。每种方式都有(对应的)好处和(额外的)开销。保持在栈上的值,减少了 GC 的压力。但是需要存储,跟踪和维护不同的副本。将值放在堆上的指针,会增加 GC 的压力。然而,也有它的好处,只有一个值需要存储,跟踪和维护。(其实,)最关键的是如何保持正确地、一致地以及均衡(开销)地使用。


via: https://www.ardanlabs.com/blog/2017/05/language-mechanics-on-escape-analysis.html

作者:William Kennedy 译者:gogeof 校对:polaris1119

期待该系列后续文章,欢迎关注我哦!



Tags:Go 语言   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
作为开发人员,微信生态我们不能无视,微信提供的开放能力,我们应该有所了解。微信支付作为重要的一部分,平时工作中可能难免会遇到。Go 作为一门新语言,微信支付没有提供 Go 的 SD...【详细内容】
2020-09-04  Tags: Go 语言  点击:(52)  评论:(0)  加入收藏
Go语言开发团队花了很长时间来解决当今软件开发人员面对的问题。开发人员在为项目选择语言时,不得不在快速开发和性能之间做出选择。C和C++这类语言提供了很快的执行速度,而Ru...【详细内容】
2020-08-27  Tags: Go 语言  点击:(53)  评论:(0)  加入收藏
在 2 世纪, 发送机密消息的一个有效方法就是对每个字母进行位移, 使得 'a' 变为 'd' , 'b' 变为 'e' , 依次类推。 这样处理产生的结果看上去...【详细内容】
2020-08-11  Tags: Go 语言  点击:(61)  评论:(0)  加入收藏
大家好,从今天起,我们一起来学习 Echo 框架。这几年,随着 Go 语言的发展,各种 Web 框架也出现了。常言道:没有选择是一种无奈,有选择是一种痛苦。所以,大家总是问:Web 框架大佬推荐...【详细内容】
2020-06-30  Tags: Go 语言  点击:(78)  评论:(0)  加入收藏
背景介绍直播系统主要是以内容为主,好的内容可以吸引用户来欣赏,也能为公司带来可观的收益,既然有传播的入口,那么必然有负面内容的出现,随着平台用户量不断扩大,内容的监管也是必...【详细内容】
2020-06-02  Tags: Go 语言  点击:(60)  评论:(0)  加入收藏
每到节假日和过年,需要外出通行的人几乎都会遇到一个问题:抢火车票!当全国上亿人都在固定的时间段抢票,服务器动辄就要承受上百万级并发的情况时,你就会明白,一个支持高并发的服务...【详细内容】
2020-05-10  Tags: Go 语言  点击:(63)  评论:(0)  加入收藏
编写健壮且高性能的网络服务需要付出大量的努力。提高服务性能的方式有很多种,比如优化应用层的代码,更进一步,还可以看看垃圾回收器,操作系统,网络传输,以及部署我们服务的硬件是否有优化空间。...【详细内容】
2019-12-30  Tags: Go 语言  点击:(76)  评论:(0)  加入收藏
反射是 Go 语言比较重要的一个特性之一,虽然在大多数的应用和服务中并不常见,但是很多框架都依赖 Go 语言的反射机制实现一些动态的功能。作为一门静态语言,Golang 在设计上都...【详细内容】
2019-11-20  Tags: Go 语言  点击:(195)  评论:(0)  加入收藏
我终于又开始使用 Go 语言编程了。虽然我在前两年多的时间里积极参与这个项目,但从 2012 年起,我就基本没有参加过这个项目。最初,我之所以做出贡献,是因为我是贝尔实验室 Plan...【详细内容】
2019-09-23  Tags: Go 语言  点击:(109)  评论:(0)  加入收藏
这篇文章是来自最新 justforfunc 中同标题的一段。这个程序的代码可以在 justforfunc 仓库 中找到。问题陈述想象一下,对于下面的代码段,你如何将其中所有的标识符都提取出来...【详细内容】
2019-08-07  Tags: Go 语言  点击:(246)  评论:(0)  加入收藏
▌简易百科推荐
zip 是一种常见的归档格式,本文讲解 Go 如何操作 zip。首先看看 zip 文件是如何工作的。以一个小文件为例:(类 Unix 系统下)$ cat hello.textHello!执行 zip 命令进行归档:$ zip...【详细内容】
2021-12-17  Go语言中文网    Tags:Go语言   点击:(13)  评论:(0)  加入收藏
大家好,我是 polarisxu。前段时间,Russ Cox 明确了泛型相关的事情,原计划在标准库中加入泛型相关的包,改放到 golang.org/x/exp 下。目前,Go 泛型的主要设计者 ianlancetaylor 完...【详细内容】
2021-11-30  Go语言中文网    Tags:slices 包   点击:(24)  评论:(0)  加入收藏
前言最近因为项目需要写了一段时间的 Go ,相对于 Java 来说语法简单同时又有着一些 Python 之类的语法糖,让人大呼”真香“。 但现阶段相对来说还是 Python 写的多一些,偶尔还...【详细内容】
2021-11-25  crossoverJie    Tags:Go   点击:(29)  评论:(0)  加入收藏
go-micro是基于 Go 语言用于开发的微服务的 RPC 框架,主要功能如下:服务发现,负载均衡 ,消息编码,请求/响应,Async Messaging,可插拔接口,最后这个功能牛p安装步骤安装proto...【详细内容】
2021-09-06    石老师小跟班  Tags:go-micro   点击:(197)  评论:(0)  加入收藏
GoLand 2021.2 EAP 5 现已发布。用户可以从工具箱应用程序中获得 EAP 构建,也可以从官方网站手动下载。并且从此 EAP 开始,只有拥有有效的 JetBrains 帐户才能加入该计划。手...【详细内容】
2021-06-29  IT实战联盟  今日头条  Tags:GoLand   点击:(185)  评论:(0)  加入收藏
作者:HDT3213今天给大家带来的开源项目是 Godis:一个用 Go 语言实现的 Redis 服务器。支持: 5 种数据结构(string、list、hash、set、sortedset) 自动过期(TTL) 发布订阅、地理位...【详细内容】
2021-06-18  HelloGitHub  今日头条  Tags:Go   点击:(125)  评论:(0)  加入收藏
统一规范篇合理规划目录本篇主要描述了公司内部同事都必须遵守的一些开发规矩,如统一开发空间,既使用统一的开发工具来保证代码最后的格式的统一,开发中对文件和代码长度的控制...【详细内容】
2021-05-18  1024课堂    Tags:Go语言   点击:(232)  评论:(0)  加入收藏
闭包概述 闭包不是Go语言独有的概念,在很多编程语言中都有闭包 闭包就是解决局部变量不能被外部访问的一种解决方案 是把函数当作返回值的一种应用 代码演示总体思想:在函数...【详细内容】
2021-05-14  HelloGo  今日头条  Tags:Go语言   点击:(223)  评论:(0)  加入收藏
一时想不开,想了解一下Go语言,于是安装了并体验了一下。下载1. 进入golang.google.cn 点击Download Go 2.选择对应的操作系统,点击后开始下载。 安装1. windows下执行傻瓜式安...【详细内容】
2021-05-12  程序员fearlazy  fearlazy  Tags:Go语言   点击:(237)  评论:(0)  加入收藏
1.简介channel是Go语言的一大特性,基于channel有很多值得探讨的问题,如 channel为什么是并发安全的? 同步通道和异步通道有啥区别? 通道为何会阻塞协程? 使用通道导致阻塞的协程...【详细内容】
2021-05-10  程序员麻辣烫  今日头条  Tags:Go通道   点击:(274)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条