通过禁止比较让 Go 二进制文件变小
创始人
2024-03-02 04:58:29
0

大家常规的认知是,Go 程序中声明的类型越多,生成的二进制文件就越大。这个符合直觉,毕竟如果你写的代码不去操作定义的类型,那么定义一堆类型就没有意义了。然而,链接器的部分工作就是检测没有被程序引用的函数(比如说它们是一个库的一部分,其中只有一个子集的功能被使用),然后把它们从最后的编译产出中删除。常言道,“类型越多,二进制文件越大”,对于多数 Go 程序还是正确的。

本文中我会深入讲解在 Go 程序的上下文中“相等”的意义,以及为什么像这样的修改会对 Go 程序的大小有重大的影响。

定义两个值相等

Go 的语法定义了“赋值”和“相等”的概念。赋值是把一个值赋给一个标识符的行为。并不是所有声明的标识符都可以被赋值,如常量和函数就不可以。相等是通过检查标识符的内容是否相等来比较两个标识符的行为。

作为强类型语言,“相同”的概念从根源上被植入标识符的类型中。两个标识符只有是相同类型的前提下,才有可能相同。除此之外,值的类型定义了如何比较该类型的两个值。

例如,整型是用算数方法进行比较的。对于指针类型,是否相等是指它们指向的地址是否相同。映射和通道等引用类型,跟指针类似,如果它们指向相同的地址,那么就认为它们是相同的。

上面都是按位比较相等的例子,即值占用的内存的位模式是相同的,那么这些值就相等。这就是所谓的 memcmp,即内存比较,相等是通过比较两个内存区域的内容来定义的。

记住这个思路,我过会儿再来谈。

结构体相等

除了整型、浮点型和指针等标量类型,还有复合类型:结构体。所有的结构体以程序中的顺序被排列在内存中。因此下面这个声明:

type S struct {
    a, b, c, d int64
}

会占用 32 字节的内存空间;a 占用 8 个字节,b 占用 8 个字节,以此类推。Go 的规则说如果结构体所有的字段都是可以比较的,那么结构体的值就是可以比较的。因此如果两个结构体所有的字段都相等,那么它们就相等。

a := S{1, 2, 3, 4}
b := S{1, 2, 3, 4}
fmt.Println(a == b) // 输出 true

编译器在底层使用 memcmp 来比较 a 的 32 个字节和 b 的 32 个字节。

填充和对齐

然而,在下面的场景下过分简单化的按位比较的策略会返回错误的结果:

type S struct {
    a byte
    b uint64
    c int16
    d uint32
}

func main()
    a := S{1, 2, 3, 4}
    b := S{1, 2, 3, 4}
    fmt.Println(a == b) // 输出 true
}

编译代码后,这个比较表达式的结果还是 true,但是编译器在底层并不能仅依赖比较 ab 的位模式,因为结构体有填充

Go 要求结构体的所有字段都对齐。2 字节的值必须从偶数地址开始,4 字节的值必须从 4 的倍数地址开始,以此类推 1 。编译器根据字段的类型和底层平台加入了填充来确保字段都对齐。在填充之后,编译器实际上看到的是 2 :

type S struct {
    a byte
    _ [7]byte // 填充
    b uint64
    c int16
    _ [2]int16 // 填充
    d uint32
}

填充的存在保证了字段正确对齐,而填充确实占用了内存空间,但是填充字节的内容是未知的。你可能会认为在 Go 中 填充字节都是 0,但实际上并不是 — 填充字节的内容是未定义的。由于它们并不是被定义为某个确定的值,因此按位比较会因为分布在 s 的 24 字节中的 9 个填充字节不一样而返回错误结果。

Go 通过生成所谓的相等函数来解决这个问题。在这个例子中,s 的相等函数只比较函数中的字段略过填充部分,这样就能正确比较类型 s 的两个值。

类型算法

呵,这是个很大的设置,说明了为什么,对于 Go 程序中定义的每种类型,编译器都会生成几个支持函数,编译器内部把它们称作类型的算法。如果类型是一个映射的键,那么除相等函数外,编译器还会生成一个哈希函数。为了维持稳定,哈希函数在计算结果时也会像相等函数一样考虑诸如填充等因素。

凭直觉判断编译器什么时候生成这些函数实际上很难,有时并不明显,(因为)这超出了你的预期,而且链接器也很难消除没有被使用的函数,因为反射往往导致链接器在裁剪类型时变得更保守。

通过禁止比较来减小二进制文件的大小

现在,我们来解释一下 Brad 的修改。向类型添加一个不可比较的字段 3 ,结构体也随之变成不可比较的,从而强制编译器不再生成相等函数和哈希函数,规避了链接器对那些类型的消除,在实际应用中减小了生成的二进制文件的大小。作为这项技术的一个例子,下面的程序:

package main

import "fmt"

func main() {
    type t struct {
        // _ [0][]byte // 取消注释以阻止比较
        a byte
        b uint16
        c int32
        d uint64
    }
    var a t
    fmt.Println(a)
}

用 Go 1.14.2(darwin/amd64)编译,大小从 2174088 降到了 2174056,节省了 32 字节。单独看节省的这 32 字节似乎微不足道,但是考虑到你的程序中每个类型及其传递闭包都会生成相等和哈希函数,还有它们的依赖,这些函数的大小随类型大小和复杂度的不同而不同,禁止它们会大大减小最终的二进制文件的大小,效果比之前使用 -ldflags="-s -w" 还要好。

最后总结一下,如果你不想把类型定义为可比较的,可以在源码层级强制实现像这样的奇技淫巧,会使生成的二进制文件变小。


附录:在 Brad 的推动下,Cherry ZhangKeith Randall 已经在 Go 1.15 做了大量的改进,修复了最严重的故障,消除了无用的相等和哈希函数(虽然我猜想这也是为了避免这类 CL 的扩散)。

相关文章:

  1. Go 运行时如何高效地实现映射(不使用泛型)
  2. 空结构体
  3. 填充很难
  4. Go 中有类型的 nil(2)

  1. 在 32 位平台上 int64unit64 的值可能不是按 8 字节对齐的,因为平台原生的是以 4 字节对齐的。查看 议题 599 了解内部详细信息。
  2. 32 位平台会在 ab 的声明中填充 _ [3]byte。参见前一条。
  3. Brad 使用的是[0]func(),但是所有能限制和禁止比较的类型都可以。添加了一个有 0 个元素的数组的声明后,结构体的大小和对齐不会受影响。

via: https://dave.cheney.net/2020/05/09/ensmallening-go-binaries-by-prohibiting-comparisons

作者:Dave Cheney 选题:lujun9972 译者:lxbwolf 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

相关内容

苹果macOS 27公测:...
苹果今日正式发布 macOS Golden Gate 首个公开测试...
2026-07-15 13:42:19
赛博菩萨 Cloudfla...
互联网最大的「保安」,现在要当最大的「收银员」了。 作者| 靖宇 ...
2026-07-05 16:49:54
AI抢电,戈壁造电
作者 | 辰纹 来源 | 洞见新研社 过去三年,全球AI 赛道的胜...
2026-06-26 22:38:15
开源固件Coreboot推...
IT之家 6 月 26 日消息,据科技媒体 Phoronix 昨天...
2026-06-26 14:30:03
macOS 27 Gold...
谷歌Gemini的macOS客户端近期因不遵守Mac应用规范而受到...
2026-06-20 12:54:18
全国先发开源鸿蒙机器人操作...
5月25日消息,全国先发开源鸿蒙机器人操作系统M-Robots O...
2026-05-26 02:05:34

热门资讯

“Linux 中国” 开源社区... 这是一个艰难的决定 —— 这不是调侃,而是当你真正做出改变了你十几年生活轨迹的决定时,你就会明白这个...
硬核观察 #1263 德国铁路... #1 德国铁路公司招聘 Windows for Workgroups 3.11 管理员该职位要求了解...
人工智能教程(七):Sciki... 在本系列的 上一篇文章 中,我们用 TensorFlow 构建了第一个神经网络,然后还通过 Kera...
关闭 WSL 中正在运行的 L... 以下是使用 WSL 关闭在 Windows 内运行的 Linux 系统的不同方法。你使用 WSL 在...
MX Linux 23.2 “... 最新的 MX Linux 23.2 小版本更新带来了基本的改进和新增功能。MX Linux 推出了 ...
硬核观察 #1262 网络上最... #1 网络上最古老的软件存档库之一即将关闭美国新墨西哥州立大学(NMSU)最近宣布,即将于 2024...
如何在 RHEL 上通过绑定配... 在 Linux 中,你可以根据自己的需要创建高级网络设置,如 网络绑定 Network Bondin...
Linux Matrix 消息... Matrix 用户可以升级 Fractal 6 了!Fractal 是目前 最好的 Matrix 客...
如何通过 VLC 使用字幕 使用 VLC 媒体播放器播放和管理字幕的新手指南。我是一个超级动漫迷,如果你想观看最新的剧集,那么你...
硬核观察 #1261 X 计划... #1 X 计划在奥斯汀建立内容审核总部据报道,该部门将招聘 100 名全职员工,将主要关注 CSE ...