返回顶部
首页 > 资讯 > 后端开发 > GO >Go语言中的逃逸分析究竟是什么?
  • 281
分享到

Go语言中的逃逸分析究竟是什么?

2024-04-02 19:04:59 281人浏览 安东尼
摘要

目录1、逃逸分析介绍2、Go中内存分配在哪里?3、Go与c++内存分配的区别4、逃逸分析骚操作5、逃逸分析引申示例说明 1、逃逸分析介绍 学计算机的同学都知道,在编译原理中,分析指针

1、逃逸分析介绍

学计算机的同学都知道,在编译原理中,分析指针动态范围的方法称之为逃逸分析。通俗来讲,当一个对象的指针被多个方法或线程引用时,我们称这个指针发生了“逃逸”。

Go语言的逃逸分析是编译器执行静态代码分析后,对内存管理进行的优化和简化,它可以决定一个变量是分配到堆还栈上。

写过C/C++的小伙伴应该知道,使用比较经典的mallocnew函数可以在堆上分配一块内存,这块内存的使用和回收(销毁)的任务在程序员中,处理不当,很可能会发生内存泄露。

2、Go中内存分配在哪里?

但是在Go语言中,基本不用担心内存泄露的问题,因为内存回收Go语言中已经帮我们处理了(GC回收机制)。虽然也有new函数,但是使用new函数得到的内存不一定就在堆上。堆和栈的区别对程序员“模糊化”了,当然这一切都是Go编译器在背后帮我们完成的。

Go语言逃逸分析最基本的原则是:如果一个函数返回对一个变量的引用,那么它就会发生逃逸。

简单来说,编译器会分析代码的特征和代码生命周期,Go中的变量只有在编译器可以证明在函数返回后不会再被引用的,才分配到栈上,其他情况下都是分配到堆上。

Go语言里没有一个关键字或者函数可以直接让变量被编译器分配到堆上,相反,编译器通过分析代码来决定将变量分配到何处。

对一个变量取地址,可能会被分配到堆上。但是编译器进行逃逸分析后,如果考察到在函数返回后,此变量不会被引用,那么还是会被分配到栈上。

编译器会根据变量是否被外部引用来决定是否逃逸:

  • 如果在函数外面没有引用到,则优先放到栈区中;
  • 如果在函数外面存在引用的可能,则就会放到堆区中;

当我们写C/C++代码时,为了提高效率,会经常将pass-by-value(传值)提升成pass-by-reference,企图避免构造函数的运行,并且直接返回一个指针。

你一定还记得,这里隐藏了一个很大的坑:在函数内部定义了一个局部变量,然后返回这个局部变量的地址(指针)。这些局部变量是在栈上分配的(静态内存分配),一旦函数执行完毕,变量占据的内存会被销毁,任何对这个返回值作的动作(如解引用),都将扰乱程序的运行,甚至导致程序直接崩溃。比如下面的这段代码:


int *foo ( void )   
{   
    int t = 3;
    return &t;
}

有些同学可能知道上面这个坑,用了个更聪明的做法:在函数内部使用new函数构造一个变量(动态内存分配),然后返回此变量的地址。因为变量是在堆上创建的,所以函数退出时不会被销毁。

但是,这样就行了吗?new出来的对象该在何时何地delete呢?调用者可能会忘记delete或者直接拿返回值传给其他函数,之后就再也不能delete它了,也就是发生了内存泄露。关于这个坑,大家可以去看看《Effective C++》条款21,讲得非常好!

3、Go与C++内存分配的区别

上面讲的C/C++中会遇到的问题,在Go中作为一个语言特性被大力推崇,可以解决以上的难点!

C/C++中的动态分配的内存需要我们手动来释放,这样会带来一个问题:有些内存处理不当或回收不及时,导致内存泄露。

但是这样的好处是:开发人员可以自己管理内存。

Go的垃圾回收,让堆和栈对程序员保持透明。真正解放了程序员的双手,让他们可以专注于业务,“高效”地完成代码编写。把那些内存管理的复杂机制交给编译器,而程序员可以去享受生活。

4、逃逸分析骚操作

逃逸分析这种“骚操作”把变量合理地分配到它该去的地方。即使你是用new申请到的内存,如果我发现你竟然在退出函数后没有用了,那么就把你丢到栈上,毕竟栈上的内存分配比堆上快很多;反之,即使你表面上只是一个普通的变量,但是经过逃逸分析后发现在退出函数之后还有其他地方在引用,那我就把你分配到堆上。

如果变量都分配到堆上,堆不像栈可以自动清理。它会引起Go频繁地进行垃圾回收,而垃圾回收会占用比较大的系统开销(占用CPU容量的25%)。

堆和栈相比,堆适合不可预知大小的内存分配。但是为此付出的代价是分配速度较慢,而且会形成内存碎片。栈内存分配则会非常快。栈分配内存只需要两个CPU指令:“PUSH”和“RELEASE”,分配和释放;而堆分配内存首先需要去找到一块大小合适的内存块,之后要通过垃圾回收才能释放。

通过逃逸分析,可以尽量把那些不需要分配到堆上的变量直接分配到栈上,堆上的变量少了,会减轻分配堆内存的开销,同时也会减少gc的压力,提高程序的运行速度。

5、逃逸分析引申示例说明

引申1:如何查看某个变量是否发生了逃逸?两种方法:使用go命令,查看逃逸分析结果;反汇编源码

比如用这个例子:


package main
import "fmt"
func foo() *int {
    t := 3
    return &t;
}
func main() {
    x := foo()
    fmt.Println(*x)
}

使用go命令:


go build -gcflags '-m -l' main.go


加-l是为了不让foo函数被内联。得到如下输出:


# 命令行变量
src/main.go:7:9: &t escapes to heap
src/main.go:6:7: moved to heap: t
src/main.go:12:14: *x escapes to heap
src/main.go:12:13: main ... argument does not escape

foo函数里的变量t逃逸了,和我们预想的一致。让我们不解的是为什么main函数里的x也逃逸了?这是因为有些函数参数为interface类型,比如fmt.Println(a …interface{}) ,编译期间很难确定其参数的具体类型,也会发生逃逸。

反汇编代码比较难理解,这里就不讲了。

引申2:下面代码中的变量发生逃逸了吗?

先来看示例1:


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

分析:Go语言函数传递都是通过值的,调用函数的时候,直接在栈上copy出一份参数,不存在逃逸。

 再来看示例二:


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

分析:identity函数的输入直接当成返回值了,因为没有对z作引用,所以z没有逃逸。对x的引用也没有逃出main函数的作用域,因此x也没有发生逃逸。

 继续看示例三:


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

分析:z是对x的拷贝,ref函数中对z取了引用,所以z不能放在栈上,否则在ref函数之外,通过引用如何找到z,所以z必须要逃逸到堆上。仅管在main函数中,直接丢弃了ref的结果,但是Go的编译器还没有那么智能,分析不出来这种情况。而对x从来就没有取引用,所以x不会发生逃逸。

还有示例四:如果对一个结构体成员赋引用如何?


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
}

分析:refStruct函数对y取了引用,所以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
}

分析:main函数里对i取了引用,并且把它传给了refStruct函数,i的引用一直在main函数的作用域用,因此i没有发生逃逸。和上一个例子相比,有一点小差别,但是导致的程序效果是不同的:例子4中,i先在main的栈帧中分配,之后又在refStruct栈帧中分配,然后又逃逸到堆上,到堆上分配了一次,共3次分配。本例中,i只分配了一次,然后通过引用传递。

到此这篇关于Go语言中的逃逸分析究竟是什么?的文章就介绍到这了,更多相关Go语言中的逃逸内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

您可能感兴趣的文档:

--结束END--

本文标题: Go语言中的逃逸分析究竟是什么?

本文链接: https://lsjlt.com/news/137274.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Go语言中的逃逸分析究竟是什么?
    目录1、逃逸分析介绍2、Go中内存分配在哪里?3、Go与C++内存分配的区别4、逃逸分析骚操作5、逃逸分析引申示例说明 1、逃逸分析介绍 学计算机的同学都知道,在编译原理中,分析指针...
    99+
    2024-04-02
  • Go语言中内存管理逃逸分析详解
    目录1. 前言2. 逃逸策略3. 逃逸场景3.1 指针逃逸3.2 栈空间不足逃逸3.3 动态类型逃逸3.4 闭包引用对象逃逸4.逃逸总结5. 注意事项1. 前言 所谓的逃逸分析(Es...
    99+
    2023-03-15
    Go 内存管理逃逸分析 Go 内存管理逃逸 Go 内存管理
  • Go语言中内存管理逃逸的方法是什么
    本篇内容介绍了“Go语言中内存管理逃逸的方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1. 前言所谓的逃逸分析(Escape an...
    99+
    2023-07-05
  • 简述Java编程语言中的逃逸分析
            大家一般认为new出来的对象都是被分配在堆上,但这并不是完全正确,通过对Java对象分配过程分析,我们发现对象除了可以被分配在堆上,还可以在栈或TLAB中...
    99+
    2023-05-31
    java 编程语言 逃逸分析
  • Go语言泛型分析:其泛型的实质究竟何在?
    Go语言泛型分析:其泛型的实质究竟何在? 近年来,Go语言一直被诟病缺乏泛型支持,这成为了许多开发者探讨的焦点。随着Go语言社区的不断发展和对泛型的需求日益增长,Go语言团队在1.18...
    99+
    2024-03-15
    分析 go语言 泛型
  • 单线程究竟是Go语言的特色吗?
    单线程究竟是Go语言的特色吗? Go语言作为一种新兴的编程语言,以其简洁、高效、并发等特点吸引着越来越多的开发者。其中一个备受关注的特性就是其采用单线程模型来处理并发。那么,单线程究竟...
    99+
    2024-03-15
    go语言 单线程 特色 标准库
  • Go语言底层实现原理揭秘:底层语言究竟是什么?
    Go语言底层实现原理揭秘:底层语言究竟是什么? 在计算机科学领域中,底层语言通常指的是可以直接与硬件交互的编程语言,它可以更加精细地控制计算机的底层资源,包括内存、寄存器等。作为一种高...
    99+
    2024-03-07
    go语言 底层实现 原理揭秘
  • Go语言底层实现探秘:究竟使用了什么?
    Go语言底层实现探秘:究竟使用了什么? Go语言作为一门高效、简洁的编程语言,深受开发者的喜爱。其背后的底层实现一直是广大开发者想要深入了解的话题。在本文中,我们将探究Go语言底层实现...
    99+
    2024-04-02
  • GO 语言在 Laravel 中的使用方式究竟如何?
    GO语言是一种快速、高效、可靠的编程语言。它是由Google开发的,并在2012年正式发布。在Laravel中,GO语言可以用来处理各种任务,例如处理HTTP请求、运行后台任务、数据处理和存储等。 GO语言的优点是它的速度和效率,它能够高效...
    99+
    2023-10-13
    对象 laravel 自然语言处理
  • 详解Objective-C中的语法糖@{}究竟是什么
    最近在技术群里有一个群友提出了一个问题,就是为什么下面代码打印的结果不一样? NSMutableDictionary *mDic1 = [NSMutableDictionary ...
    99+
    2022-05-23
    objective-c 语法糖 @{}
  • 缓存究竟是如何影响Go语言索引算法的?
    缓存对于Go语言中的索引算法来说非常重要,因为索引算法的效率通常取决于它们对数据的访问模式。在此文章中,我们将讨论缓存如何影响Go语言索引算法,并提供一些演示代码来说明这个问题。 一、缓存的基本概念 缓存是一种将数据存储在快速访问存储器中的...
    99+
    2023-07-28
    索引 编程算法 缓存
  • Go 语言在分布式系统中的应用价值究竟有多大?
    随着互联网的发展,分布式系统逐渐成为了企业开发中的重要组成部分。在分布式系统中,多个计算机节点通过网络连接相互通信,共同完成一个任务。这种架构的优点在于可以提高系统的可扩展性、可靠性和性能。 而在分布式系统的开发中,选择一门适合的编程语言...
    99+
    2023-10-30
    分布式 numy ide
  • Go语言并发编程对于大数据分析究竟有多大帮助?
    随着数据量的不断增加,大数据分析越来越成为了一种必要的技能。而对于大数据分析来说,速度、效率和可扩展性是非常重要的因素。Go语言因其高效、并发性能强的特性,成为了大数据处理的一种热门语言。那么,Go语言并发编程对于大数据分析究竟有多大帮助...
    99+
    2023-06-27
    并发 shell 大数据
  • 探究Go语言的源头:是基于什么语言的?
    探究Go语言的源头:是基于什么语言的? Go语言是一种近年来备受关注的编程语言,它的出现给程序员带来了全新的编程体验。作为一种现代化的编程语言,Go在设计之初融合了多种语言的优点,同时...
    99+
    2024-04-02
  • Go语言中的数组究竟有什么神奇之处?学习笔记揭秘!
    在Go语言中,数组是一种非常重要的数据类型,它可以存储一组相同类型的数据。相比其他编程语言,Go语言中的数组具有许多独特的特性,这使得它成为了许多开发者的首选。 本文将为大家详细介绍Go语言中数组的特性和使用方法,并提供一些演示代码供大家...
    99+
    2023-10-20
    数组 学习笔记 http
  • GRPC是否仅支持Go语言?深入研究及分析
    小伙伴们有没有觉得学习Golang很有意思?有意思就对了!今天就给大家带来《GRPC是否仅支持Go语言?深入研究及分析》,以下内容将会涉及到,若是在学习中对其中部分知识点有疑问,或许看了本文就能帮到...
    99+
    2024-04-04
  • GO 语言中的 SHELL 是什么?
    随着计算机技术的不断发展,人们对于计算机语言的需求也越来越高。其中,Go 语言被越来越多的程序员所青睐。在 Go 语言中,SHELL 是一个非常重要的概念。本文将会介绍 SHELL 在 Go 语言中的作用以及如何使用。 一、SHELL 是...
    99+
    2023-09-05
    自然语言处理 shell 对象
  • 异步编程和接口在 Go 语言中的应用:究竟有何不同?
    在现代软件开发中,异步编程和接口是两个非常重要的概念。在 Go 语言中,这两个概念得到了广泛应用。但是,它们之间究竟有何不同呢?本文将对异步编程和接口在 Go 语言中的应用进行详细介绍,并通过演示代码来说明它们之间的区别。 异步编程 异步...
    99+
    2023-10-30
    异步编程 数据类型 接口
  • 深入探究:Go语言RPC框架的优缺点分析
    近年来,随着云计算和分布式系统的不断发展,远程过程调用(Remote Procedure Call,RPC)作为一种重要的通信方式,受到越来越多开发者的关注。作为一种快速、方便、高效的...
    99+
    2024-02-27
    go语言 rpc框架 优缺点 并发请求
  • go语言中泛型是的是什么
    今天小编给大家分享的是go语言中泛型是的是什么,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。一定会有所收获的哦。在go语言中,泛型就是编写模板适应所有类型,只有在具体使用时才定义具体变量类型;通过引入类...
    99+
    2023-07-04
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作