Go的编译可执行文件的大小的原因

Kar*_*Rao 72 executable go

我编写了一个hello world Go程序,它在我的linux机器上生成本机可执行文件.但我很惊讶地看到简单的Hello world Go程序的大小,它是1.9MB!

为什么Go中这样一个简单程序的可执行文件如此庞大?

icz*_*cza 69

这个确切的问题出现在官方常见问题解答中:为什么我的琐碎程序如此大的二进制文件?

引用答案:

在GC工具链(连接体5l,6l和8l)做静态链接.因此,所有Go二进制文件都包括Go运行时,以及支持动态类型检查,反射甚至恐慌时间堆栈跟踪所需的运行时类型信息.

在Linux上使用gcc静态编译和链接的简单C"hello,world"程序大约为750 kB,包括一个实现printf.使用的等效Go程序fmt.Printf大约为1.9 MB,但其中包括更强大的运行时支持和类型信息.

因此,Hello World的本机可执行文件是1.9 MB,因为它包含一个运行时,它提供垃圾收集,反射和许多其他功能(您的程序可能不会真正使用它,但它就在那里).以及fmt用于打印"Hello World"文本(以及其依赖项)的包的实现.

现在尝试以下操作:fmt.Println("Hello World! Again")在程序中添加另一行并再次编译.结果不会是2x 1.9MB,但仍然只有1.9 MB!是的,因为所有已使用的库(fmt及其依赖项)和运行时已经添加到可执行文件中(因此只需添加几个字节来打印刚刚添加的第二个文本).

  • 与glibc静态链接的AC“ hello world”程序为750K,因为glibc显然不是为静态链接而设计的,在某些情况下甚至无法正确进行静态链接。与musl libc静态链接的“ hello world”程序为14K。 (3认同)
  • @DavidSpector 是的,“hello world”可能不会使用添加到二进制文件中的“花哨”功能,但“hello world”通常不是您愿意编写的应用程序。因此,从“hello world”中优化未使用的运行时功能根本不值得。任何像样的应用程序都会(直接或间接)使用反射、GC、调度程序等。 (3认同)
  • @DavidSpector Go 运行时是一个复杂的机器:内存分配器、GC、调度器......你不能只是“摇动那棵树”。 (2认同)
  • @DavidSpector 静态链接它可以防止您遇到运行时版本更改和破坏程序的问题。此外,它还使交叉编译变得更加容易,因为您不必安装所有系统库即可在不同的架构上编译您的 go 程序。这些当然是权衡,但如果你考虑到 1.9MB 作为硬盘空间甚至通过互联网传输的成本,这是完全值得的,这并不多。 (2认同)

Jop*_*ppe 29

考虑以下程序:

package main

import "fmt"

func main() {
    fmt.Println("Hello World!")
}
Run Code Online (Sandbox Code Playgroud)

如果我在我的Linux AMD64机器(Go 1.9)上构建它,就像这样:

$ go build
$ ls -la helloworld
-rwxr-xr-x 1 janf group 2029206 Sep 11 16:58 helloworld
Run Code Online (Sandbox Code Playgroud)

我得到一个大小约为2 Mb的二进制文件.

其原因(已在其他答案中解释)是我们使用的"fmt"包非常大,但二进制文件也没有被剥离,这意味着符号表仍然存在.如果我们改为指示编译器去除二进制文件,它将变得更小:

$ go build -ldflags "-s -w"
$ ls -la helloworld
-rwxr-xr-x 1 janf group 1323616 Sep 11 17:01 helloworld
Run Code Online (Sandbox Code Playgroud)

但是,如果我们重写程序以使用内置函数print而不是fmt.Println,如下所示:

package main

func main() {
    print("Hello World!\n")
}
Run Code Online (Sandbox Code Playgroud)

然后编译它:

$ go build -ldflags "-s -w"
$ ls -la helloworld
-rwxr-xr-x 1 janf group 714176 Sep 11 17:06 helloworld
Run Code Online (Sandbox Code Playgroud)

我们最终得到了一个更小的二进制文件.这是我们可以得到的小而不诉诸像UPX打包的技巧,所以Go运行时的开销大约是700 Kb.

  • UPX 压缩二进制文件并在执行时即时解压缩它们。在不解释它的作用的情况下,我不会忽视它是一个技巧,因为它在某些情况下很有用。二进制大小有所减小,但会牺牲启动时间和 RAM 使用量;此外,性能也会受到轻微影响。举个例子,可执行文件可以缩小到其(剥离)大小的 30%,并且运行时间会延长 35 毫秒。 (5认同)

Von*_*onC 8

请注意,golang/go项目中的问题6853会跟踪二进制大小问题.

例如,提交a26c01a(针对Go 1.4)将hello world提交70kB:

因为我们不会将这些名称写入符号表.

考虑到1.5的编译器,汇编器,链接器和运行时将完全在Go中,您可以期待进一步的优化.

  • @EJP,Go简单的一部分是不使用共享库.实际上,Go根本没有依赖关系,它使用普通的系统调用.只需部署一个二进制文件就可以了.它会严重损害语言和它的生态系统,如果不是这样的话. (8认同)
  • 静态链接二进制文件经常被遗忘的方面是它可以在完全空的Docker容器中运行它们.从安全角度来看,这是理想的选择.当容器是空的,你也许可以打破(如果静态链接二进制有缺陷),但由于没有什么容器中被发现,攻击停在那里. (8认同)
  • @EJP:为什么他们需要使用共享库? (3认同)
  • 我不明白他的实现语言与它有什么关系。他们需要使用共享库。有点不可思议的是,他们在这个时代还没有。 (2认同)