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

时间:2015-02-18 04:47:11

标签: go executable

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

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

3 个答案:

答案 0 :(得分:56)

这个确切的问题出现在官方常见问题解答中:Why is my trivial program such a large binary?

引用答案:

  

gc工具链中的链接器(5l6l8l)执行静态链接。因此,所有Go二进制文件都包含Go运行时,以及支持动态类型检查,反射甚至恐慌时间堆栈跟踪所需的运行时类型信息。

     

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

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

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

答案 1 :(得分:21)

考虑以下计划:

package main

import "fmt"

func main() {
    fmt.Println("Hello World!")
}

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

$ go build
$ ls -la helloworld
-rwxr-xr-x 1 janf group 2029206 Sep 11 16:58 helloworld

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

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

$ go build -ldflags "-s -w"
$ ls -la helloworld
-rwxr-xr-x 1 janf group 1323616 Sep 11 17:01 helloworld

但是,如果我们重写程序以使用内置函数print而不是fmt.Println,就像这样:

package main

func main() {
    print("Hello World!\n")
}

然后编译它:

$ go build -ldflags "-s -w"
$ ls -la helloworld
-rwxr-xr-x 1 janf group 714176 Sep 11 17:06 helloworld

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

答案 2 :(得分:7)

请注意,issue 6853golang/go project会跟踪二进制大小问题。

例如,commit a26c01a(针对Go 1.4)将你好世界减少70kB

  

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

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


2016年更新Go 1.7:已经过优化:请参阅“Smaller Go 1.7 binaries”。

但是这些日子(2019年4月),最需要的是runtime.pclntab 请参阅 Why are my Go executable files so large? Size visualization of Go executables using D3 中的“Raphael ‘kena’ Poss”。

  

目前还没有太多记录,但Go源代码中的这条评论暗示了它的目的:

// A LineTable is a data structure mapping program counters to line numbers.
     

此数据结构的目的是使Go运行时系统能够在崩溃时或通过runtime.GetStack API发出内部请求时生成描述性堆栈跟踪。

     

所以看起来很有用。但为什么这么大呢?

     

隐藏在上述链接源文件中的网址https://golang.org/s/go12symtab会重定向到一个文档,该文档解释了Go 1.0和1.2之间发生的情况。换句话说:

     

在1.2之前,Go链接器正在发出一个压缩的行表,程序会在运行时初始化时对其进行解压缩。

     

在Go 1.2中,决定将可执行文件中的行表预先扩展为适合在运行时直接使用的最终格式,而无需额外的解压缩步骤。

     

换句话说,Go团队决定将可执行文件放大,以节省初始化时间。

     

另外,从数据结构来看,除了每个函数的大小外,它在编译二进制文件中的总体大小似乎是程序中函数的超线性。

https://science.raphael.poss.name/go-executable-size-visualization-with-d3/size-demo-ss.png