Go语言编译后可执行文件体积巨大的原因是什么?

huangapple go评论80阅读模式
英文:

Reason for huge size of compiled executable of Go

问题

我编写了一个在我的Linux机器上生成本机可执行文件的Hello World Go程序。但是我对这个简单的Hello World Go程序的大小感到惊讶,它有1.9MB!

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

英文:

I complied a hello world Go program which generated native executable on my linux machine. But I was surprised to see the size of the simple Hello world Go program, it was 1.9MB !

Why is it that the executable of such a simple program in Go is so huge?

答案1

得分: 114

这个确切的问题在官方FAQ中有提到:为什么我的简单程序会生成如此大的二进制文件?

引用答案:

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

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

因此,你的Hello World的本地可执行文件大小为1.9 MB,因为它包含了一个运行时,提供了垃圾回收、反射和许多其他功能(尽管你的程序可能并没有真正使用这些功能,但它们确实存在)。还有你用来打印“Hello World”文本的fmt包的实现(以及它的依赖项)。

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

英文:

This exact question appears in the official FAQ: Why is my trivial program such a large binary?

Quoting the answer:

>The linkers in the gc tool chain (5l, 6l, and 8l) do static linking. All Go binaries therefore include the Go run-time, along with the run-time type information necessary to support dynamic type checks, reflection, and even panic-time stack traces.
>
> A simple C "hello, world" program compiled and linked statically using gcc on Linux is around 750 kB, including an implementation of printf. An equivalent Go program using fmt.Printf is around 1.9 MB, but that includes more powerful run-time support and type information.

So the native executable of your Hello World is 1.9 MB because it contains a runtime which provides garbage collection, reflection and many other features (which your program might not really use, but it's there). And the implementation of the fmt package which you used to print the "Hello World" text (plus its dependencies).

Now try the following: add another fmt.Println("Hello World! Again") line to your program and compile it again. The result will not be 2x 1.9MB, but still just 1.9 MB! Yes, because all the used libraries (fmt and its dependencies) and the runtime are already added to the executable (and so just a few more bytes will be added to print the 2nd text which you just added).

答案2

得分: 63

考虑以下程序:

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

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

造成这个问题的原因(在其他答案中已经解释过)是我们使用了相当大的"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运行时的开销大约为700KB。

英文:

Consider the following program:

package main

import "fmt"

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

If I build this on my Linux AMD64 machine (Go 1.9), like this:

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

I get a a binary that is about 2 Mb in size.

The reason for this (which has been explained in other answers) is that we are using the "fmt" package which is quite large, but the binary has also not been stripped and this means that the symbol table is still there. If we instead instruct the compiler to strip the binary, it will become much smaller:

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

However, if we rewrite the program to use the builtin function print, instead of fmt.Println, like this:

package main

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

And then compile it:

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

We end up with an even smaller binary. This is as small as we can get it without resorting to tricks like UPX-packing, so the overhead of the Go-runtime is roughly 700 Kb.

答案3

得分: 17

请注意,二进制大小问题在golang/go项目问题6853中进行了跟踪。

例如,提交a26c01a(适用于Go 1.4)将hello world减小了70kB

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

考虑到编译器、汇编器、链接器和1.5版本的运行时将完全使用Go语言编写,您可以期待进一步的优化。


更新2016年Go 1.7:已经进行了优化,请参见“更小的Go 1.7二进制文件”。

但是在现在(2019年4月),占用最多空间的是runtime.pclntab
请参阅Raphael ‘kena’ Poss的“使用D3可视化Go可执行文件的大小:Go可执行文件的大小可视化”。

> 尽管没有太好的文档,但是Go源代码中的这个注释表明了它的目的:
>
> // A LineTable is a data structure mapping program counters to line numbers.
>
> 这个数据结构的目的是使Go运行时系统能够在崩溃或通过runtime.GetStack API的内部请求时生成描述性的堆栈跟踪。
>
> 所以它似乎很有用。但为什么它这么大呢?
>
> 在前面链接的源文件中隐藏的URL https://golang.org/s/go12symtab 重定向到了一个解释Go 1.0和1.2之间发生了什么的文档。简而言之:
>
> 在1.2之前,Go链接器会发出一个压缩的行表,程序会在运行时初始化时解压缩它。
>
> 在Go 1.2中,决定将行表在可执行文件中预先展开为最终格式,以便在运行时直接使用,而不需要额外的解压缩步骤。
>
> 换句话说,Go团队决定增加可执行文件的大小以节省初始化时间。
>
> 此外,从数据结构来看,它在编译后的二进制文件中的总大小与程序中函数的数量超线性相关,还与每个函数的大小有关。

Go语言编译后可执行文件体积巨大的原因是什么?

英文:

Note that the binary size issue is tracked by issue 6853 in the golang/go project.

For instance, commit a26c01a (for Go 1.4) cut hello world by 70kB:

> because we don't write those names into the symbol table.

Considering the compiler, assembler, linker, and runtime for 1.5 will be
entirely in Go, you can expect further optimization.


Update 2016 Go 1.7: this has been optimized: see "Smaller Go 1.7 binaries".

But these day (April 2019), what takes the most place is runtime.pclntab.
See "Why are my Go executable files so large? Size visualization of Go executables using D3" from Raphael ‘kena’ Poss.

> It is not too well documented however this comment from the Go source code suggests its purpose:
>
> // A LineTable is a data structure mapping program counters to line numbers.
>
> The purpose of this data structure is to enable the Go runtime system to produce descriptive stack traces upon a crash or upon internal requests via the runtime.GetStack API.
>
> So it seems useful. But why is it so large?
>
> The URL https://golang.org/s/go12symtab hidden in the aforelinked source file redirects to a document that explains what happened between Go 1.0 and 1.2. To paraphrase:
>
> prior to 1.2, the Go linker was emitting a compressed line table, and the program would decompress it upon initialization at run-time.
>
> in Go 1.2, a decision was made to pre-expand the line table in the executable file into its final format suitable for direct use at run-time, without an additional decompression step.
>
> In other words, the Go team decided to make executable files larger to save up on initialization time.
>
> Also, looking at the data structure, it appears that its overall size in compiled binaries is super-linear in the number of functions in the program, in addition to how large each function is.

Go语言编译后可执行文件体积巨大的原因是什么?

huangapple
  • 本文由 发表于 2015年2月18日 12:47:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/28576173.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定