当前位置: 首页 > 科技观察

通过禁止比较使Go二进制文件更小

时间:2023-03-18 20:19:34 科技观察

众所周知,Go程序中声明的类型越多,生成的二进制文件就越大。这是直观的。毕竟,如果你写的代码不对定义的类型进行操作,那么定义一堆类型是没有意义的。然而,链接器的部分工作是检测程序未引用的函数(假设它们是库的一部分,其中仅使用了功能的子集)并将它们从最终编译输出中删除。“类型越多,二进制文件越大”这句格言适用于大多数Go程序。在本文中,我将深入探讨“相等”在Go程序上下文中的含义,以及为什么这样的修改会对Go程序的大小产生重大影响。定义两个值相等Go的语法定义了“赋值”和“相等”的概念。赋值是为标识符赋值的行为。并非所有声明的标识符都可以赋值,例如常量和函数。相等是通过检查标识符的内容是否相等来比较两个标识符的行为。作为一种强类型语言,“相同性”的概念植根于标识符的类型。两个标识符只有在它们属于同一类型时才能相同。除其他事项外,值的类型定义了如何比较该类型的两个值。例如,使用算术方法比较整数类型。对于指针类型,相等性是指它们指向的地址是否相同。像指针这样的引用类型,如指针,如果指向同一个地址就被认为是相同的。以上都是按位比较相等的例子,即值所占内存的位模式相同,则这些值相等。这称为memcmp,内存比较,其中通过比较两个内存区域的内容来定义相等性。请牢记这一思路,稍后我会回来讨论它。结构相等性除了整数、浮点类型和指针等标量类型外,还有复合类型:结构。所有结构都按程序顺序排列在内存中。所以下面的语句:typeSstruct{a,b,c,dint64}会占用32字节的内存空间;a占8个字节,b占8个字节,以此类推。Go的规则是说如果一个struct的所有字段都是可比较的,那么这个struct的值也是可比较的。因此,如果两个结构的所有字段都相等,则两个结构相等。a:=S{1,2,3,4}b:=S{1,2,3,4}fmt.Println(a==b)//输出true编译器在底部使用memcmp来比较32a字节和b的32字节。填充和对齐然而,简单的按位比较策略在以下情况下返回错误结果:typeSstruct{abytebuint64cint16duint32}funcmain()a:=S{1,2,3,4}b:=S{1,2,3,4}fmt.Println(a==b)//Outputtrue}编译代码后,这个比较表达式的结果仍然为true,但是编译器底层你不能't仅仅依赖于比较a和b的位模式,因为该结构具有填充。Go要求结构的所有字段对齐。2字节的值必须从偶数地址开始,4字节的值必须从4的倍数地址开始,依此类推1.编译器添加padding保证字段按照字段类型和底层平台。填充之后,编译器实际看到的是2:typeSstruct{abyte_[7]byte//paddingbuint64cint16_[2]int16//paddingduint32}填充的存在保证了字段都是正确对齐的,而padding确实占用内存空间,但是padding字节的内容是未知的。你可能认为Go中的填充字节全为0,但事实并非如此——填充字节的内容是未定义的。由于它们没有被定义为某个确定的值,因此按位比较会返回不正确的结果,因为分布在s的24个字节中的9个填充字节是不同的。Go通过生成所谓的相等函数来解决这个问题。在这个例子中,s的相等函数只比较函数中的字段并跳过填充,这样可以正确比较两个类型为s的值。类型算法,这是一个很大的设置,它解释了为什么对于Go程序中定义的每个类型,编译器都会生成几个支持函数,编译器在内部调用该类型的算法。如果类型是映射的键,编译器除了生成相等函数外,还会生成一个散列函数。为了保持稳定性,哈希函数在计算结果时也会考虑填充等因素,就像等式函数一样。当编译器生成这些函数时,实际上很难凭直觉来判断,有时它并不明显,(因为)它比你预期的要多,而且链接器也很难消除没有被使用的函数,因为反射往往会导致链接器在裁剪类型时要更加保守。通过禁用比较减少二进制大小现在,让我们解释Brad的修改。给类型加上一个不可比较的字段3,结构就变得不可比较了,从而强制编译器不再生成相等函数和哈希函数,避免了那些类型被链接器淘汰,减少了生成二进制文件的大小。作为这种技术的一个例子,下面的程序:使用Go1.14.2(darwin/amd64)编译的tfmt.Println(a)},大小从2174088减少到2174056,节省了32个字节。这32字节的节省单独看来可能微不足道,但考虑到程序中的每种类型及其传递闭包都会生成等式和散列函数,以及它们的依赖性,其大小随类型大小和复杂性而变化,禁用它们会大大减少最终的大小二进制,甚至比以前使用-ldflags="-s-w"更好。总而言之,如果您不想将类型定义为可比较的,您可以在源代码级别强制使用这样的技巧,这将使生成的二进制文件更小。