本文记录学习到的一些与 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 | go-build % tree |
可以看到,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 | x86_64 linux |
如果在项目中依赖了 C 代码,则不能够使用 go build 工具来实现交叉编译,需要要借助第三方工具 xgo。
编译期赋值
go 语言可以实现在编译时对变量赋值,以完成在代码中增加版本等信息。该功能需要借助 -ldflags 选项。
1 | var( |
直接使用 -ldflags 选项即可,不需要再借助 flag parse。
1 | go build -ldflags \ |
这是通过将值写入符号表来完成的,符号表用来存储程序中的标识符(即常量和变量的类型、值等相关数据)。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 | // +build linux darwin |
build tags 需要遵循以下原则:
- 每一行注释以
+build
开始; - 每个选项由数字和字母组成,如果开头为
!
代表反义; - 选项之间相隔
' '
代表或关系,选项之间相隔,
代表与关系; - 不同行注释代表与关系
上面示例中代表在 linux 或 darwin 平台且架构为 x86 的情况下才会编译。
文件名后缀
类似测试文件的 _test
后缀,go 语言中也可以通过添加后缀的方式来实现条件编译。文件名后缀的命名方式为:filename(_$GOOS)(_$GOARCH).go
。其中 GOOS 如果出现,必须排列在 GOARCH 前面。
文件名后缀只能够实现在特定条件下进行编译,而不能实现在特定条件下取消编译。
Go Plugin
参考链接