我编译了一个hello world Go程序,该程序在linux机器上生成了本机可执行文件。但是我很惊讶地看到简单的Hello world Go程序的大小为1.9MB!
为什么Go中如此简单的程序的可执行文件如此庞大?
我编译了一个hello world Go程序,该程序在linux机器上生成了本机可执行文件。但是我很惊讶地看到简单的Hello world Go程序的大小为1.9MB!
为什么Go中如此简单的程序的可执行文件如此庞大?
dotnet publish -r win-x64 -p:publishsinglefile=true -p:publishreadytorun=true -p:publishtrimmed=true
生成一个约26MB的二进制文件!
Answers:
这个确切的问题出现在官方常见问题解答中:为什么我的琐碎程序这么大的二进制文件?
引用答案:
在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
及其依赖项)和运行时已添加到可执行文件中(因此,仅会再添加几个字节以打印刚添加的第二个文本)。
考虑以下程序:
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。
请注意,golang / go项目中的问题6853跟踪了二进制大小问题。
例如,提交a26c01a(对于Go 1.4)将hello world减少70kB:
因为我们不将这些名称写入符号表。
考虑到1.5的编译器,汇编器,链接器和运行时将完全在Go中,您可以期望进一步的优化。
Update 2016 Go 1.7:已优化:请参阅“ Small Go 1.7二进制文件”。
但是,这些天(2019年4月)占据了最大的位置runtime.pclntab
。
请参阅“所以去的大?大小可视化的可执行文件使用D3为什么我转到可执行文件从”拉斐尔“凯纳” POSS。
它的文档记录不是很好,但是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团队决定使可执行文件更大,以节省初始化时间。
同样,从数据结构来看,除了每个函数有多大之外,在编译的二进制文件中,其总体大小似乎在程序中的函数数量上是超线性的。