浅谈Golang 不同版本的defer

简介: 浅谈Golang 不同版本的defer

前言

关于defer底层刨析请看专栏这一篇博文

Golang defer底层原理剖析

Go1.12

编译后的伪指令

defer指令对应到两部分内容

  1. deferproc负责把要执行的函数保存起来,我们称之为defer注册。
  2. 返回之前通过deferreturn执行注册的defer函数。
  • 注册defer会创建一个_defer结构体,并且头插到runtime.g._defer链表中
  • _defer结构体是在堆中,在执行时参数变量需要在堆栈直接拷贝
  • deferreturn执行时依次执行runtime.g._defer链表每一项
// runtime/runtime2.go
type g struct {
  ...
  _defer       *_defer // defer 链表
  ...
}

步骤解析

deferproc

  1. 从defer pool预分配不同规格的defer,不满足时再创建,用完再放回池中。
  2. 进行堆分配_defer结构体,将参数拷贝到堆上
  3. 将该结构体头插到runtime.g._defer链表中

deferreturn

  1. 执行defer注册的funcval,将堆上的参数拷贝到栈上进行执行。
  2. 如果执行的是闭包,堆上分配捕获变量的地址,执行时通过指针加偏移量找到被捕获的变量进行执行
  3. 形如defer A(B(a))的情况,函数A需要依赖B的返回值进行堆分配内存大小,所以注册defer的时候直接会执行B(a)

存在的问题

  1. defer结构体堆分配,参数需要进行堆栈间拷贝。 GC
  2. 链表注册defer信息,执行比较慢。

Go1.13

性能提升:30%

runtime.deferprocStack

  1. 编译阶段:将defer的参数分配到栈帧的局部变量部分。
  2. defer结构体分配到栈上,通过deferprocStack注册到defer链表中,以减少defer的堆分配
  3. 但是对于循环defer或隐式循环注册还是需要进行堆分配
  4. defer结构体中加入heap字段区分是否为堆分配

runtime.deferreturn

  1. defer函数执行时拷贝参数,不过不是在堆栈之间,而是从栈上的局部变量空间,拷贝到参数空间。
  2. 分配到堆上的defer1.12还是进行参数的堆栈间拷贝

Go1.14

性能提升一个数量级

  1. 普通的 defer A(a,b) 会进行分配局部变量a,b,并在函数最后执行
  2. 对于带有条件的defer A2() 函数,需要到执行期间才知道是否需要执行
  3. 目标就是通过编译是插入代码,将defer在函数内进行展开执行,就不用defer结构体测创建和注册defer链表

引入df变量进行标记df的每一个bit是0或1,来进行决定defer是否需要执行

执行过了标记为0,避免重复执行。

官方称1.14版本为open coded defer

不适用于循环defer和隐式注册,所以go1.12还是进行保留。

问题

panic()和runtime.GoExit() 退出需要执行defer链表,但是由于没注册链表,所以需要栈扫描的方式来找到未注册defer函数。所以panic更慢了。但是这样做是因为 整体性能优化 > panic发生的概率


目录
相关文章
|
Go 开发者
go-carbon 2.2.9 版本发布, 轻量级、语义化、对开发者友好的 Golang 时间处理库
carbon 是一个轻量级、语义化、对开发者友好的 golang 时间处理库,支持链式调用。
57 0
|
Go 开发者
go-carbon 2.2.7 版本发布, 轻量级、语义化、对开发者友好的Golang时间处理库
carbon 是一个轻量级、语义化、对开发者友好的 golang 时间处理库,支持链式调用。
69 0
|
Go 开发者
go-carbon 2.2.6 版本发布, 轻量级、语义化、对开发者友好的Golang时间处理库
carbon 是一个轻量级、语义化、对开发者友好的 golang 时间处理库,支持链式调用。 目前已被 [awesome-go](https://github.com/avelino/awesome-go#date-and-time "awesome-go") 收录,如果您觉得不错,请给个 star 吧
60 0
|
1月前
|
Go Python
通过 atexit 模块让 Python 实现 Golang 的 defer 功能
通过 atexit 模块让 Python 实现 Golang 的 defer 功能
20 2
|
3月前
|
Serverless Go
Golang 开发函数计算问题之defer 中的 recover() 没有捕获到 如何解决
Golang 开发函数计算问题之defer 中的 recover() 没有捕获到 如何解决
|
3月前
|
人工智能 数据库连接 Go
golang defer 详解
golang defer 详解
47 0
|
6月前
|
Go 开发者
Golang深入浅出之-Go语言 defer、panic、recover:异常处理机制
Go语言中的`defer`、`panic`和`recover`提供了一套独特的异常处理方式。`defer`用于延迟函数调用,在返回前执行,常用于资源释放。它遵循后进先出原则。`panic`触发运行时错误,中断函数执行,直到遇到`recover`或程序结束。`recover`在`defer`中捕获`panic`,恢复程序执行。注意避免滥用`defer`影响性能,不应对可处理错误随意使用`panic`,且`recover`不能跨goroutine捕获panic。理解并恰当使用这些机制能提高代码健壮性和稳定性。
132 2
|
Linux 编译器 Go
Golang 语言怎么安装多个 Golang 版本的环境?
Golang 语言怎么安装多个 Golang 版本的环境?
231 0
|
安全 Go
Golang 语言中的 defer 怎么使用?
Golang 语言中的 defer 怎么使用?
46 0
|
6月前
|
Java Go Rust
Golang每日一练(leetDay0095) 第一个错误的版本、完全平方数
Golang每日一练(leetDay0095) 第一个错误的版本、完全平方数
44 0
Golang每日一练(leetDay0095) 第一个错误的版本、完全平方数