Go 学习笔记-Go 编译器简介

简介: Go 学习笔记-Go 编译器简介

1.抽象语法树构建

  • 编译器前端必须构建程序的中间表示形式,以便在编译器中间阶段及后端使用。
  • 抽象语法树(Abstract Syntax Tree,AST)是一种常见的树状结构的中间态。
  • 在Go语言源文件中的任何一种import、type、const、func声明都是一个根节点,在根节点下包含当前声明的子节点。
  • 核心逻辑代码位于 go/src/cmd/compile/internal/gc/noder.go 中。

  • 每个节点都包含了当前节点属性的Op字段,定义在 go/src/cmd/compile/internal/gc/syntax.go 中,以O开头。
  • 与词法解析阶段中的token相同的是,Op 字段也是一个整数。不同的是,每个 Op 字段都包含了语义信息,例如,当一个节点的 Op 操作为 OAS 时,该节点代表的语义为 Left:=Right,而当节点的操作为 OAS2 时,代表的语义为x,y,z=a,b,c。
func (p *noder) decls(decls []syntax.Decl) (l []*Node) {  var cs constState
  for _, decl := range decls {      p.setlineno(decl)      switch decl := decl.(type) {      case *syntax.ImportDecl:          p.importDecl(decl)
      case *syntax.VarDecl:          l = append(l, p.varDecl(decl)...)
      case *syntax.ConstDecl:          l = append(l, p.constDecl(decl, &cs)...)
      case *syntax.TypeDecl:          l = append(l, p.typeDecl(decl))
      case *syntax.FuncDecl:          l = append(l, p.funcDecl(decl))
      default:          panic("unhandled Decl")      }  }
  return}

2.类型检查

  • 完成抽象语法树的初步构建后,就进入类型检查阶段遍历节点树并决定节点的类型。
  • 这其中包括了语法中明确指定的类型,例如 var a int,也包括了需要通过编译器类型推断得到的类型。
  • 在类型检查阶段,会对一些类型做特别的语法或语义检查。例如:引用的结构体字段是否是大写可导出的?数组字面量的访问是否超过了其长度?数组的索引是不是正整数?
  • 在类型检查阶段还会进行其他工作。例如:计算编译时常量、将标识符与声明绑定等。类型检查的核心逻辑位于 go/src/cmd/compile/internal/gc/typecheck.go 中。

3.变量捕获

  • 变量捕获主要是针对闭包场景而言的,由于闭包函数中可能引用闭包外的变量,因此变量捕获需要明确在闭包中通过值引用或地址引用的方式来捕获变量。
  • 类型检查阶段完成后,Go语言编译器将对抽象语法树进行分析及重构,从而完成一系列优化。
  • 变量捕获的核心逻辑位于 go/src/cmd/compile/internal/gc/closure.go 文件的 capturevars 函数中。
package main
import (    "fmt")
func main()  {    a := "qinshixian"    b := make(map[string]int)    c := "haoweilai"    go func() {        fmt.Println(a)        fmt.Println(b)        fmt.Println(c)    }()    a = "asddss"}

使用 go tool compile -m=2 getVar.go| grep capturing 命令可以查看变量捕获信息,如下图:

从上图可以看出变量 a 采用 ref 引用传递方式,变量 bc 采用 value 值传递的方式。

4.函数内联

  • 函数内联指将较小的函数直接组合进调用者的函数。
  • 函数内联的优势在于,可以减少函数调用带来的开销。
  • 对于 Go 语言来说,函数调用的成本在于参数与返回值栈复制、较小的栈寄存器开销以及函数序言部分的检查栈扩容(Go语言中的栈是可以动态扩容的)。
  • Go语言编译器会计算函数内联花费的成本,只有执行相对简单的函数时才会内联。
  • 函数内联的核心代码位于 go/src/cmd/compile/internal/gc/inl.go 中。
  • 当函数内部有 forrangegoselect 等语句时,该函数不会被内联。
  • 若希望程序中所有的函数都不执行内联操作,那么可以添加编译器选项 “-l”。
  • 函数内联效率提升举例:
package main
import "testing"
//go:noinlinefunc max(a, b int) int {    if a > b {        return a    }    return b}
var Result int
func BenchmarkMax(b *testing.B) {    var r int    for i := 0; i < b.N; i++ {        r = max(-1, i)    }    Result = r}

Tips://go:noinline 表示当前函数禁止函数内联优化。

加上注释 //go:noinline 运行 go test leetcode_test.go -bench=. 如下图:

没有加上注释 //go:noinline 运行 go test leetcode_test.go -bench=. 如下图:

函数内部有 forrangegoselect 等语句时,如下图:

若想查看函数是否可以使用函数内联,可使用 命令 go tool compile -m=2 leetcode_test.go,如下图所示:

4.逃逸分析

  • 逃逸分析也是 Go 编译阶段中的优化,用于标识变量内存应该被分配在栈区还是堆区。
  • 若函数返回了一个栈上的对象指针,函数执行完成后,栈被销毁,访问被销毁栈上的对象指针就会出现问题,逃逸分析能识别这种问题,将该变量放置到堆区,并借助 Go 运行时的垃圾回收机制自动释放内存。
  • 编译器会尽可能地将变量放置到栈中,因为栈中的对象随着函数调用结束会被自动销毁,减轻运行时分配和垃圾回收的负担。
  • 逃逸分析核心代码位置在 go/src/cmd/compile/internal/gc/escape .go 文件中。
  • 不管是字符串、数组字面量,还是通过 new、make 标识符创建的对象,都既可能被分配到栈中,也可能被分配到堆中。分配时,遵循以下两个原则:
(1)原则1:指向栈上对象的指针不能被存储到堆中
(2)原则2:指向栈上对象的指针不能超过该栈对象的生命周期
package main
var n *int
func escape(){    a := 100    n = &a}
func main()  {    escape()}

使用命令 go tool compile -m getVar.go,如下图所示:

其中变量 n 是一个 int 型指针,若 a 被分配到栈中,变量 n 超出了 a 的生命周期范围,违背了上述原则2,所以 a 需要被分配到堆中。


相关文章
|
9月前
|
Java Go 开发者
Docker容器技术简介及其与Go语言的结合点
【2月更文挑战第23天】本文首先概述了Docker容器技术的核心概念和优势,接着探讨了Go语言与Docker容器技术的结合点。通过阐述Docker的轻量级、可移植性和版本控制等特性,以及Go语言在容器化应用中的优势,本文旨在说明两者结合能够实现更高效、灵活的应用开发和部署。
|
9月前
|
监控 安全 Java
Go语言学习笔记(一)
Go语言学习笔记(一)
130 1
|
3月前
|
缓存 监控 前端开发
在 Go 语言中实现 WebSocket 实时通信的应用,包括 WebSocket 的简介、Go 语言的优势、基本实现步骤、应用案例、注意事项及性能优化策略,旨在帮助开发者构建高效稳定的实时通信系统
本文深入探讨了在 Go 语言中实现 WebSocket 实时通信的应用,包括 WebSocket 的简介、Go 语言的优势、基本实现步骤、应用案例、注意事项及性能优化策略,旨在帮助开发者构建高效稳定的实时通信系统。
198 1
Go语言的条件控制语句及循环语句的学习笔记
本文是Go语言的条件控制语句和循环语句的学习笔记,涵盖了if语句、if-else语句、if嵌套语句、switch语句、select语句以及for循环和相关循环控制语句的使用方法。
Go语言的条件控制语句及循环语句的学习笔记
|
5月前
|
存储 Go
Go: struct 结构体类型和指针【学习笔记记录】
本文是Go语言中struct结构体类型和指针的学习笔记,包括结构体的定义、成员访问、使用匿名字段,以及指针变量的声明使用、指针数组定义使用和函数传参修改值的方法。
|
6月前
|
中间件 Go 数据库
slog 简介:用于 Go 的结构化日志
slog 简介:用于 Go 的结构化日志
|
5月前
|
人工智能 算法 搜索推荐
Go学习笔记-代码调
近年来,人工智能技术飞速发展,Cody作为由Sourcegraph开发的一款AI驱动编码助手,应运而生。它不仅提供代码预测与补全,还能深度理解代码上下文,为开发者提供准确建议,提升编码效率和质量。Cody能识别潜在错误并提出修复建议,缩短调试时间,同时进行智能代码审查,帮助优化代码结构和风格。未来,随着AI技术进步,Cody将不断学习优化,成为开发者不可或缺的伙伴,推动编程领域的创新与发展。
41 0
|
6月前
|
安全 编译器 Go
Go 编译器的独特优势详解
【8月更文挑战第31天】
63 0
|
8月前
|
消息中间件 存储 Kafka
go语言并发实战——日志收集系统(二) Kafka简介
go语言并发实战——日志收集系统(二) Kafka简介
157 1
|
9月前
|
安全 Go 开发者
Golang深入浅出之-Go语言并发编程面试:Goroutine简介与创建
【4月更文挑战第22天】Go语言的Goroutine是其并发模型的核心,是一种轻量级线程,能低成本创建和销毁,支持并发和并行执行。创建Goroutine使用`go`关键字,如`go sayHello(&quot;Alice&quot;)`。常见问题包括忘记使用`go`关键字、不正确处理通道同步和关闭、以及Goroutine泄漏。解决方法包括确保使用`go`启动函数、在发送完数据后关闭通道、设置Goroutine退出条件。理解并掌握这些能帮助开发者编写高效、安全的并发程序。
120 1