0%

go 编译相关内容的记录

本文记录学习到的一些与 go 语言编译相关的内容。

编译速度

go 是编译速度比较快的语言之一,主要有以下几个原因,参考链接

  • 语法较为简单,不允许重载,不需要复杂的语义分析
  • 使用包管理,而非头文件形式,避免了解析头文件
  • 不使用虚拟机,编译时不需要加载 VM
  • 大部分包都使用静态链接的方式
  • 优化器更为简单,编译期优化较少,同时导致性能一定程度上下降,参考链接

编译缓存

在 C/C++ 中,大型项目的构建往往需要依赖 make 和 CMake 工具来提供编译缓存,以实现增量编译的功能。在 golang 中,从 1.10 版本后编译器支持了编译缓存的功能,可以进行增量构建。编译过程中的缓存文件将会被存放在环境路径 GOCACHE 下。此外,go test 也支持在特定条件下缓存 test 结果,从而加快执行测试的速度。

增量编译

go 语言的增量编译是以 package 为单位的,一个 package 内任意一个文件的变更都会导致整个 package 以及依赖该 package 的所有 package重新编译 。但是,golang 的文件修改判别依据相比 C/C++ 体系更加合理。在 qt5 中,使用 qmake 进行增量编译时,依赖于文件的修改时间戳,如果只是对文件进行了格式调整,也会导致项目重新编译。而在 golang 中,文件修改的判别依据是文件内容是否改变,只修改文件的行数、增加注释、删除后恢复内容都不会导致package 重新编译

在 GOCACHE 路径下,执行 tree 命令,可以得到如下输出(截取部分):

1
2
3
4
5
6
7
8
9
10
11
go-build % tree

.
├── 00
│   ├── 000d954e953e73d70b0fa00ceaf3c68f3adbb5164e5381754493a4e592ad714f-a
│   └── 0068e9620015a00b938707781ad8b56fdd86fe4a09eeb7802d874bd041f5ad79-a
├── 01
│   ├── 0107518e42b66ffb35a353ffd437cb2a6f448b07ad8fceba401e7bc1692c17e3-d
│   ├── 01126d3106b1aee39e975dcd10a2ac64f170d28ca14b37c8c5e7cec7817e60fd-a
...
└── trim.txt

可以看到,golang 中使用了内容摘要算法来组织存储,每一个目录下存储的文件名前两位字符都对应着文件夹名称。在编译过程中,go 编译器会将编译后 package 的 .a 文件求取 64 位摘要值,并将其名称命名为”摘要值-a” 的形式,并存储在摘要值前两位对应的文件夹下。

go test 缓存

在go 1.10中,go test 同样可以被缓存介入,不过需要满足一定的条件,go release note 中给出了缓存介入条件:

  • 本次测试的执行程序以及命令行(及参数)与之前的一次test运行匹配;
  • 上次测试执行时的文件和环境变量在本次没有发生变化;
  • 测试结果是成功的;
  • 以package list mode运行测试;
  • go test的命令行参数使用”-cpu, -list, -parallel, -run, -short和 -v”的一个子集时。

其中 package list mode 是指运行某个 package 的测试程序,而不是以当前目录为参数。

交叉编译

go build 工具链是支持交叉编译的,在不直接调用 C 代码的情况下,可以直接使用 go build 命令来完成交叉编译。在使用交叉编译时,需要通过变更 GO ENV 来控制编译输出。常用的环境变量如下:

  • CGO_ENABLE:关闭 CGO 选项,需要关闭,因为交叉编译不支持 CGO;
  • GOOS:编译的目标操作系统,如 linux、darwin、windows;
  • GOARCH:编译的架构,如 386、amd64、arm。

交叉编译示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# x86_64 linux
CGO_ENABLED=0
GOOS=linux
GOARCH=amd64
go build main.go

# x86_64 Windows
CGO_ENABLED=0
GOOS=windows
GOARCH=amd64
go build main.go

# arm Macos
CGO_ENABLED=0
GOOS=darwin
GOARCH=arm
go build main.go

如果在项目中依赖了 C 代码,则不能够使用 go build 工具来实现交叉编译,需要要借助第三方工具 xgo。

编译期赋值

go 语言可以实现在编译时对变量赋值,以完成在代码中增加版本等信息。该功能需要借助 -ldflags 选项。

1
2
3
4
5
var(
var version = "v0.0.0"
buildTime string
buildVersion string
)

直接使用 -ldflags 选项即可,不需要再借助 flag parse。

1
2
3
4
5
6
7
8
go build -ldflags \ 
"-X main.version=v0.0.1 -X main.dateTime=`date +%Y-%m-%d,%H:%M:%S` -X main.gitTag=`git tag`"

# 输出值
version is: v0.0.1
dateTime is: 2023-01-14,22:18:54
gitTag is: v0.0.0-beta

这是通过将值写入符号表来完成的,符号表用来存储程序中的标识符(即常量和变量的类型、值等相关数据)。go 语言编译期过程可以简化理解为:在编译期将部分的变量的值修改,相当于改变了变量的默认值。

条件编译

go 语言中不支持 define,但是可以依赖build tags或文件后缀的方式来实现不同平台的条件编译。

build tags

build tags 是一种特殊的注释,它必须位于 package 声明的上方,并且后跟一个空行。当一个包被编译时,编译器会根据构建标签的内容来判断该包是否需要编译。

build tags 可以指定以下内容:

  • 操作系统,环境变量中GOOS的值;
  • 操作系统的架构,环境变量中GOARCH的值;
  • 使用的编译器,gc或者gccgo
  • 是否开启CGO,cgo
  • golang版本号, 如go1.1
  • 其它自定义标签,通过go build -tags指定的值。

以下为 build tags 的一个例子。

1
2
3
4
5
// +build linux darwin
// +build x86

package os
...

build tags 需要遵循以下原则:

  • 每一行注释以+build开始;
  • 每个选项由数字和字母组成,如果开头为!代表反义;
  • 选项之间相隔' '代表或关系,选项之间相隔,代表与关系;
  • 不同行注释代表与关系

上面示例中代表在 linux 或 darwin 平台且架构为 x86 的情况下才会编译。

文件名后缀

类似测试文件的 _test后缀,go 语言中也可以通过添加后缀的方式来实现条件编译。文件名后缀的命名方式为:filename(_$GOOS)(_$GOARCH).go。其中 GOOS 如果出现,必须排列在 GOARCH 前面。

文件名后缀只能够实现在特定条件下进行编译,而不能实现在特定条件下取消编译。

Go Plugin

参考链接