首页 > 其他分享 >Go高阶16,面试官问我go逃逸场景有哪些,我???

Go高阶16,面试官问我go逃逸场景有哪些,我???

时间:2024-06-03 11:34:39浏览次数:13  
标签:面试官 16 int javascript 逃逸 func go main

「逃逸分析」就是程序运行时内存的分配位置(栈或堆),是由编辑器来确定的,而非开发者。

什么是栈

栈只允许从线性表的同一端放入和取出数据,按照后进先出(LIFO,Last InFirst Out)的顺序,如下图:

什么是堆

对于堆在内存中的分配,我们可以类比成一个房间,分配内存时,需要找一块足够装下家具的空间来摆放家具。经过反复摆放和腾空家具后,房间里的空间会变得乱七八糟,此时再往这个空间里摆放家具会发现虽然有足够的空间,但各个空间分布在不同的区域,没有一段连续的空间来摆放家具。此时,内存分配器就需要对这些空间进行调整优化,如下图:

❝对比栈和堆可知,在编译时,一切无法确定大小或大小可以改变的数据,最好放到堆上,堆适合不可预知大小的内存分配。但是为此付出的代价是分配速度较慢,而且会形成内存碎片。 ❞

函数中申请一个新的对象:

  • 如果分配在栈中,则函数执行结束可自动将内存回收;
  • 如果分配在堆中,则函数执行结束可交给GC(垃圾回收)处理;

逃逸分析的好处应该是减少了 gc 的压力,栈的分配比堆快,性能好,如果变量都分配到栈上,可以避免 Go 频繁地进行垃圾回收,而垃圾回收会占用比较大的系统开销。

逃逸分析基本原则

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

  1. 如果函数外部没有引用,则优先放到栈中;
  2. 如果函数外部存在引用,则必定放到堆中;
  3. 如果栈上放不开,则必定放到堆上;

逃逸场景

指针逃逸

我们知道Go可以返回局部变量指针,这种情况下,函数虽然退出了,但是因为指针的存在,对象的内存不能随着函数结束而回收,因此只能分配在堆上。

代码语言:javascript 复制
package main

type Person struct {
 Name string
 Age  int
}

func PersonRegister(name string, age int) *Person {
 p := new(Person) //局部变量s逃逸到堆

 p.Name = name
 p.Age = age

 return p
}

func main() {
 PersonRegister("微客鸟窝", 18)
}

函数 PersonRegister() 内部 p 为局部变量,其值通过函数返回值返回, p 本身为一指针,其指向的内存地址不会是栈而是堆,这就是典型的逃逸案例。

通过编译参数-gcflag=-m可以查看编译过程中的逃逸分析:

代码语言:javascript 复制
$ go build -gcflags=-m
# ceshi
.\main.go:8:6: can inline PersonRegister
.\main.go:17:6: can inline main
.\main.go:18:16: inlining call to PersonRegister
.\main.go:8:21: leaking param: name
.\main.go:9:10: new(Person) escapes to heap
.\main.go:18:16: new(Person) does not escape

代码第9行显示”escapes to heap”,表示该行内存分配发生了逃逸现象。

栈空间不足逃逸

代码语言:javascript 复制
package main

func Slice() {
 s := make([]int, 1000, 1000)

 for index, _ := range s {
  s[index] = index
 }
}

func main() {
 Slice()
}

上面代码 Slice() 函数中分配了一个1000个长度的切片,是否逃逸取决于栈空间是否足够大。直接查看编译提示,如下:

代码语言:javascript 复制
$ go build -gcflags=-m
# ceshi
.\main.go:11:6: can inline main
.\main.go:4:11: make([]int, 1000, 1000) does not escape

发现并没有发生逃逸。我们把切片长度扩大10倍再试试: s := make([]int, 10000, 10000)

代码语言:javascript 复制
$ go build -gcflags=-m
# ceshi
.\main.go:11:6: can inline main
.\main.go:4:11: make([]int, 10000, 10000) escapes to heap

发现当切片长度扩大到10000时就会逃逸。当栈空间不足以存放当前对象时或无法判断当前切片长度时会将对象分配到堆中。

动态类型逃逸

在 Go 中,空接口 interface{} 可以表示任意的类型,如果函数参数为 interface{},编译期间很难确定其参数的具体类型,也会发生逃逸。

代码语言:javascript 复制
package main
import "fmt"

func main() {
 s := "wekenw"
 fmt.Println(s)
}

因为 fmt.Println() 的参数类型定义为 interface{},因此也发生了逃逸。

代码语言:javascript 复制
$ go build -gcflags=-m
# ceshi
.\main.go:6:13: inlining call to fmt.Println
.\main.go:6:13: s escapes to heap
.\main.go:6:13: []interface {}{...} does not escape
<autogenerated>:1: .this does not escape
<autogenerated>:1: .this does not escape

闭包引用对象逃逸

回Fibonacci数列的函数:

代码语言:javascript 复制
package main

func main() {
 f := fibonacci()
 for i := 0; i < 10; i++ {
  f()
 }
}
func fibonacci() func() int {
 a, b := 0, 1
 return func() int {
  a, b = b, a+b
  return a
 }
}

Fibonacci()函数中原本属于局部变量的a和b由于闭包的引用,不得不将二者放到堆上,以致产生逃逸:

代码语言:javascript 复制
$ go build -gcflags=-m
# ceshi
.\main.go:11:9: can inline fibonacci.func1
.\main.go:10:2: moved to heap: a
.\main.go:10:5: moved to heap: b
.\main.go:11:9: func literal escapes to heap

总结

  • 栈上分配内存比在堆中分配内存效率更高
  • 栈上分配的内存不需要 GC 处理,而堆需要
  • 逃逸分析目的是决定内分配地址是栈还是堆
  • 逃逸分析在编译阶段完成

传值 VS 传指针

「函数传递指针真的比传值效率高吗?如果拷贝的数据量小,由于指针传递会产生逃逸,可能会使用堆,增加垃圾回收(GC)的负担,所以传递指针不一定 是高效的。」

图片及部分相关技术知识点来源于网络搜索,侵权删!

参考文档:

http://c.biancheng.net/view/22.html

https://www.cnblogs.com/iQXQZX/p/14032884.html

https://geektutu.com/post/hpg-escape-analysis.html

《GO专家编程》

 

标签:面试官,16,int,javascript,逃逸,func,go,main
From: https://www.cnblogs.com/gongxianjin/p/18228446

相关文章

  • P1163银行贷款
    题目链接:银行贷款-洛谷思路:本题题目表述很迷糊,简单讲解一下,就是一个人去银行贷款,每月要还固定的钱,持续还n个月才能还清,求的是贷款利率;大家可能会想这不是数学题吗,直接上手求解,然后拿样例对比发现和答案对不上,这就是本题坑的地方,这银行是比高利贷还黑,他会把当月的利息加入到......
  • # 数据转换-16进制字符
    数据转换-16进制字符任务详情在openEuler(推荐)或Ubuntu或Windows(不推荐)中完成下面任务,使用git管理代码,gitcommit不能少于5次在附件中的utils.h和utils.c中完成16进制字符'0'-'9','A'-'F','a'-'f'与十六进制数据0-15的转换功能(10’):intHex2Char(unsignedintfromi......
  • Go 语言学习笔记之数组与切片
    大家好,我是码农先森。数组与切片的区别在Go语言中,数组和切片是两种不同的数据结构,它们之间有以下主要区别。参数长度:数组(Array):数组的长度是固定的,在创建时就需要指定数组的长度,无法动态改变;只有长度信息,通过len()函数获取。切片(Slice):切片是对数组的一个引用,底层使用的是......
  • 数据转换-16进制字符
    utils.h点击查看代码#ifndef_UTIL_H_#define_UTIL_H_//charHex2Char(inti);//intChar2Hex(charc);intHex2Char(intfromi,char*toc);intChar2Hex(charfromc,int*toi);intBitstr2ByteArr(char*bs,char*ba);intByteArr2Bitstr(char*ba,char*bs)......
  • 数据转换-16进制字符
    在openEuler(推荐)或Ubuntu或Windows(不推荐)中完成下面任务在附件中的utils.h和utils.c中完成16进制字符'0'-'9','A'-'F','a'-'f'与十六进制数据0-15的转换功能(10’):intHex2Char(unsignedintfromi,unsignedchar*toc);intChar2Hex(unsignedch......
  • 数据转换-16进制字符
    任务详情在openEuler(推荐)或Ubuntu或Windows(不推荐)中完成下面任务,使用git管理代码,gitcommit不能少于5次在附件中的utils.h和utils.c中完成16进制字符'0'-'9','A'-'F','a'-'f'与十六进制数据0-15的转换功能(10’):intHex2Char(unsignedintfromi,unsigne......
  • 数据转换-16进制字符
    0.在openEuler(推荐)或Ubuntu或Windows(不推荐)中完成下面任务,使用git管理代码,gitcommit不能少于5次1.在附件中的utils.h和utils.c中完成16进制字符'0'-'9','A'-'F','a'-'f'与十六进制数据0-15的转换功能(10’):```intHex2Char(unsignedintfromi,unsign......
  • 力扣1168水资源优化
    题目这个题目首先有节点,有双向边,而且要求最少总成本,那么我们最先想到的应该是最小生成树。算法逻辑 在最小生成树中有一个prim算法,个人觉得是和dijkstra非常相似甚至一模一样的,基于贪心思想的一种算法。prim的算法过程:首先找到一个一定存在的节点,然后从这个结点开始......
  • Go语言编程快速入门
    Go语言编程快速入门这个是学习B站杨旭视频做的记录,地址安装https://studygolang.com/VsCode安装Go插件ctrl+shift+p:输入go查询,选择Install/UpdateTools,全部勾选,点击OKGo代理(执行命令后重启vscode)#控制台执行命令goenv-wGO111MODULE=ongoenv-wGOPROXY=htt......
  • 面试官:说一说如何优雅的关闭线程池,我:shutdownNow,面试官:粗鲁!
    写在开头面试官:“小伙子,线程池使用过吗,来聊一聊它吧!”我:“好的,然后巴拉巴拉一顿输出之前看过的build哥线程池十八问…”面试官满意的点了点头,紧接着问道:“那你知道如何优雅的关闭线程池吗?”我:“知道知道,直接调用shutdownNow()方法就好了呀!”面试官脸色一变,微怒道:“粗......