深入 Go 底层原理(十):defer 的实现与性能开销

1. 引言

defer 语句是 Go 语言提供的一种用于延迟函数调用的机制,通常用于资源释放、解锁、记录日志等场景。它能保证在函数返回前,无论函数是正常返回还是发生 panicdefer 的调用都会被执行。

defer 使用起来非常方便,但它的实现并非零成本。了解其底层原理有助于我们评估其性能影响。

2. defer 的实现原理

在 Go 的早期版本中,defer 是通过一个链表 来实现的。每个 defer 语句都会创建一个 _defer 结构体,并将其插入到当前 goroutine 的 defer 链表的头部。函数返回时,会逆序遍历这个链表并执行调用。这种方式在 defer 数量多时性能较差。

现代 Go 版本 (Go 1.14+) 的优化 : 现代 Go 编译器对 defer 进行了大幅优化,大部分 defer 调用现在是基于栈的,而非堆分配。

  • 编译期分析 :编译器会分析函数中的 defer 语句。如果 defer 语句在 for 循环之外,且数量固定,编译器会采用基于栈的实现。

  • 栈上分配 :编译器会在函数的栈帧上预留空间来记录 defer 的信息(要调用的函数指针和参数)。

  • deferprocdeferreturn:

    • 当执行到 defer 语句时,会调用一个 deferproc 指令,将函数和参数信息存入栈上的预留空间。

    • 在函数返回前,会插入一个 deferreturn 指令,它会检查栈上的 defer 记录,并依次执行它们。

这种方式避免了堆分配和链表操作,性能几乎与直接调用相当。只有在循环中或无法在编译期确定数量的 defer,才会退回到旧的、基于堆分配的实现。

3. defer 的执行时机与陷阱
  • 参数预计算defer 语句的函数参数是在 defer 语句执行时就被计算和固定的,而不是在函数返回时。

    Go 复制代码
    func main() {
        i := 0
        defer fmt.Println("Result:", i) // i 的值 0 在这里被固定
        i++
        return // 输出: Result: 0
    }
  • return 的交互deferreturn 语句之后、函数实际返回之前执行。它可以读取和修改函数的命名返回值

    Go 复制代码
    func getNumber() (result int) {
        defer func() {
            result *= 2
        }()
        return 5 // 1. result = 5; 2. defer runs: result = 10; 3. return 10
    }
    // getNumber() 返回 10
相关推荐
小清兔2 小时前
c#基础知识
开发语言·数据库·学习·unity·c#·游戏引擎·.net
奇某人4 小时前
【语法】【C+V】本身常用图表类型用法快查【CSDN不支持,VSCODE可用】
开发语言·vscode·markdown·mermaid
做一位快乐的码农4 小时前
php程序设计之基于PHP的手工艺品销售网站/基于php在线销售系统/基于php在线购物商城系统
开发语言·php
@珍惜一生@4 小时前
Qt开源库
开发语言·qt·开源
Slaughter信仰5 小时前
深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)第四章知识点问答补充及重新排版
java·开发语言·jvm
心灵宝贝5 小时前
Mac用户安装JDK 22完整流程(Intel版dmg文件安装指南附安装包下载)
java·开发语言·macos
secondyoung5 小时前
一文丝滑使用Markdown:从写作、绘图到转换为Word与PPT
开发语言·vscode·编辑器·powerpoint·markdown·visual studio·mermaid
雨枪幻。6 小时前
spring boot开发:一些基础知识
开发语言·前端·javascript
爱炸薯条的小朋友6 小时前
C#由Dictionary不正确释放造成的内存泄漏问题与GC代系
开发语言·opencv·c#
m0_480502648 小时前
Rust 登堂 之 函数式编程(三)
开发语言·后端·rust