Go 博客
更小的 Go 1.7 二进制文件
引言
Go 设计之初是为了编写服务器程序。这也是 Go 目前最广泛的用途。因此,运行时和编译器的许多工作都集中在与服务器相关的问题上:延迟、易于部署、精确垃圾回收、快速启动时间、性能。
随着 Go 被用于更广泛的程序类型,一些新问题也必须被考虑。其中之一就是二进制文件大小。这个问题已经被关注很久了(issue #6853 是两年多前提交的),但是人们对将 Go 二进制文件部署到树莓派或移动设备等小型设备上越来越感兴趣,这意味着这个问题在 Go 1.7 版本中受到了关注。
Go 1.7 中完成的工作
Go 1.7 中的三个重大变化影响了二进制文件大小。
第一个是此版本中为 AMD64 启用的新 SSA 后端。虽然 SSA 的主要动机是提高性能,但生成的代码质量更高,体积也更小。SSA 后端将 Go 二进制文件缩小了约 5%。我们预计,在 Go 1.8 中将 ARM 和 MIPS 等更像 RISC 的架构转换为 SSA 后,将会获得更大的收益。
第二个变化是方法修剪。在 1.6 之前,所有使用过的类型的所有方法都会被保留,即使其中一些方法从未被调用过。这是因为它们可能通过接口被调用,或者使用 reflect 包动态调用。现在编译器会丢弃任何不匹配接口的未导出方法。类似地,如果程序中没有使用相应的 反射功能,链接器也可以丢弃其他导出的方法,即那些只能通过反射访问的方法。这个变化使二进制文件缩小了 5%–20%。
第三个变化是 reflect 包使用的运行时类型信息的格式更加紧凑。最初设计这种编码格式是为了使运行时和 reflect 包中的解码器尽可能简单。通过稍微降低代码的可读性,我们可以压缩格式,同时不影响 Go 程序的运行时性能。新格式使 Go 二进制文件进一步缩小了 5%–15%。为 Android 构建的库和为 iOS 构建的归档文件会进一步缩小,因为新格式包含的指针更少,而每个指针在位置无关代码中都需要动态重定位。
此外,还有许多小的改进,例如改进的接口数据布局、更好的静态数据布局以及简化的依赖关系。例如,HTTP 客户端不再链接整个 HTTP 服务器。更改的完整列表可以在 issue #6853 中找到。
结果
使用 Go 1.7 构建时,典型的程序,从小型演示程序到大型生产程序,体积大约缩小了 30%。
典型的 Hello World 程序从 2.3MB 变为 1.6MB
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
在编译时不包含调试信息时,静态链接的二进制文件现在小于一兆字节。

用于本次测试周期的大型生产程序 jujud
从 94MB 变为 67MB。
位置无关二进制文件缩小了 50%。
在位置无关可执行文件(PIE)中,只读数据段中的指针需要动态重定位。由于新的类型信息格式用节偏移量代替了指针,每个指针节省了 28 字节。
移除调试信息的位置无关可执行文件对移动开发者尤其重要,因为这是发送到手机上的程序类型。大的下载文件会带来糟糕的用户体验,所以这次的缩小是个好消息。
未来工作
一些对运行时类型信息的更改在 Go 1.7 冻结前已来不及纳入,但有望在 1.8 版本中实现,进一步缩小程序体积,特别是位置无关的程序。
所有这些更改都是保守的,在减小二进制文件大小的同时,不增加构建时间、启动时间、总体执行时间或内存使用。我们可以采取更激进的措施来减小二进制文件大小:用于压缩可执行文件的 upx 工具可以将二进制文件再缩小 50%,代价是增加启动时间并可能增加内存使用。对于极小的系统(例如可能放在钥匙链上的那种),我们可以构建一个不包含反射功能的 Go 版本,尽管这种受限的语言是否足够有用尚不清楚。对于运行时中的某些算法,当每一千字节都很重要时,我们可以使用较慢但更紧凑的实现。所有这些都需要在后续开发周期中进行更多研究。
感谢为减小 Go 1.7 二进制文件体积做出贡献的许多贡献者们!
下一篇文章:使用子测试和子基准测试
上一篇文章:Go 1.7 已发布
博客索引