Go 语言编译原理与优化
-S
打印汇编
优化和内联
内联级别:
-gcflags='-l -l'
内联级别2,更积极,可能更快,可能会制作更大的二进制文件。-gcflags='-l -l -l'
内联级别3,再次更加激进,二进制文件肯定更大,也许更快,但也许会有 bug。-gcflags=-l=4
(4个-l)在 Go 1.11 中将支持实验性的。
- 如果一个局部变量值超越了函数调用的生命周期,编译器自动将它逃逸到堆
- 如果一个通过new或make来分配的对象,在函数内即使将指针传递给了其它函数,其它函数会被内联到当前函数,相当于指针不会逃逸出本函数,最终不返回指针的话,该指针对应的值也都会分配在栈上,而不是在堆
- Go 支持 internal 和 external 两种链接方式: internal 使用 go 自身实现的 linker,external 需要启动外部的 linker
- linker 的主要工作是将
.o
(object file) 链接成最终可执行的二进制 - 对应命令:
go tool link
,对应源码:$GOROOT/src/cmd/link
- 通过
-ldflags
给链接器传参,参数详见:go tool link --help
关于 CGO
- 启用cgo可以调用外部依赖的c库
- go的编译器会判断环境变量
CGO_ENABLED
来决定是否启用cgo,默认CGO_ENABLED=1
即启用cgo - 标准库中有部分实现有两份源码,比如:
$GOROOT/src/os/user/lookup_unix.go
和$GOROOT/src/os/user/cgo_lookup_unix.go
,它们有相同的函数,但实现不一样,前者是纯go实现,后者是使用cgo调用外部依赖来实现,标准库中使用cgo比较常见的是net
包。
- link 默认使用 internal 方式
- 直接使用 go 本身的实现的 linker 来链接代码,
- 功能比较简单,仅仅是将
.o
和预编译的.a
写到最终二进制文件中(.a
文件在$GOROOT/pkg
和$GOPATH/pkg
中,其实就是.o
文件打包的压缩包,通过 可以解压出来查看)
external linking
- 会启动外部 linker (gcc/clang),通过
-ldflags '-linkmode "external"'
启用 external linking - 通过
-extldflags
给外部 linker 传参,比如:-ldflags '-linkmode "external" -extldflags "-static"'
- 如果是
external linking
,可以这样:-ldflags '-linkmode external -extldflags -static'
- 如果用默认的
internal linking
,可以这样:-ldflags '-d'
ldflags 其它常用参数
-s -w
是去除符号表和DWARF调试信息(可以减小二进制体积,但不利于调试,可在用于生产环境),示例:-ldflags '-s -w'
-X
可以给变量注入值,比如编译时用脚本动态注入当前版本和commit id
到代码的变量中,通常程序的 子命令或参数输出当前版本信息时就用这种方式实现,示例:-ldflags '-X myapp/pkg/version/version=v1.0.0'
使用 Docker 编译可以不用依赖本机 go 环境,将编译环境标准化,特别在有外部动态链接库依赖的情况下很有用,可以直接 run 一个容器来编译,给它挂载源码目录和二进制输出目录,这样我们就可以拿到编译出来的二进制了,这里以编译cfssl为例:
- Go 性能调优之 —— 编译优化: