【问题标题】:How to reduce compiled file size?如何减小编译文件的大小?
【发布时间】:2011-04-21 04:05:41
【问题描述】:

让我们比较一下 c 和 go: Hello_world.c:

#include<stdio.h>
int main(){
    printf("Hello world!");
}

Hello_world.go:

package main
import "fmt"
func main(){
    fmt.Printf("Hello world!")
}

同时编译:

$gcc Hello_world.c -o Hello_c 
$8g Hello_world.go -o Hello_go.8
$8l Hello_go.8 -o Hello_go

还有……这是什么?

$ls -ls
... 5,4K 2010-10-05 11:09 Hello_c
... 991K 2010-10-05 11:17 Hello_go

大约 1Mb 你好世界。你在跟我开玩笑吗? 我做错了什么?

(剥离 Hello_go -> 仅限 893K)

【问题讨论】:

  • 在 x86_64 Mac 上,“Hello World”二进制文件是 1.3 MB,就像我假设的 x64 Linux 机器上一样。相比之下,ARM x32 二进制文件与 x86_32 二进制文件一样大。大小很大程度上取决于相应架构的“字”长度。在 x32 机器上它是 32 位,在 x64 上它是 64 位宽。因此,x32“Hello World”二进制文件大约小了 30%。
  • @Nick:考虑到 GO 作为一种系统语言进行营销,我认为这是一个公平的问题。我在系统中工作,我们并不总是拥有 4GB 以上的 RAM 和巨大的磁盘。
  • 一个 893kb 的可执行文件与“4GB+ 的 RAM 和一个巨大的磁盘”相去甚远,正如其他人已经指出的那样,这包括静态链接的 go 运行时,很容易被排除在外。跨度>
  • 是的,这相差甚远,我知道答案,但这是一个有效的问题,“谁在乎内存消耗”的态度往往来自于在不关心内存消耗的系统上工作问题。你似乎认为无知是可以的,最好不要问问题。我再说一遍;有时〜1MB很多,你显然不在那个世界上工作。编辑 - 你在谷歌工作!哈哈. 虽然我仍然不明白“谁在乎”的态度。
  • 显然在 Java 部门 ;)

标签: go


【解决方案1】:

如果您使用的是基于 Unix 的系统(例如 Linux 或 Mac OSX),您可以尝试通过使用 -w 标志构建可执行文件来删除包含在可执行文件中的调试信息:

go build -ldflags "-w" prog.go

文件大小显着减小。

更多详情请访问 GDB 页面:http://golang.org/doc/gdb

【讨论】:

  • 使用strip 命令实现同样的效果:go build prog.go; strip prog 然后我们得到了所谓的 striped 可执行文件:ELF 64 位 LSB 可执行文件,x86-64,版本 1 ( SYSV),动态链接(使用共享库),剥离
  • 这适用于 Windows,与通过正常构建和剥离生成的文件相比,它提供的二进制文件略小。
  • @Axel:明确不支持剥离 go 二进制文件,并且在某些情况下可能会导致严重的错误。见here。
  • 当前文档列出了-w,而不是-s。不知道有什么区别,但你可能想更新你的答案:-)
  • @Dynom:-w 似乎减少了一些大小,但减少不如 -s 好。 -s 似乎暗示 -w: golang.org/cmd/link
【解决方案2】:

2016 年的答案:

1。使用 Go 1.7

2。用go build -ldflags "-s -w"编译

➜ ls -lh hello
-rwxr-xr-x 1 oneofone oneofone 976K May 26 20:49 hello*

3。然后使用upx,从1.6开始不再需要goupx。

➜ ls -lh hello
-rwxr-xr-x 1 oneofone oneofone 367K May 26 20:49 hello*

【讨论】:

  • 我想在这里补充一下,关于 UPX 的常见警告可能适用于此。有关更多信息,请参阅:stackoverflow.com/questions/353634/…(其中大部分也适用于非 Windows 操作系统)。
  • 只是在链接器标志上添加信息:-s 省略符号表和调试信息; -w 省略 DWARF 符号表。
【解决方案3】:

注意:此答案已过时

请注意,此答案已过时。请参考其他投票率较高的答案。我想删除这个帖子,但是不能删除已接受的答案。


文件变大有问题吗?我不知道 Go 但我会假设它静态链接一些运行时库,而 C 程序不是这种情况。但是,一旦您的程序变大,这可能就没什么好担心的了。

如here 所述,静态链接 Go 运行时是默认设置。该页面还告诉您如何设置动态链接。

【讨论】:

  • 有一个open issue,为Go1.5设置了里程碑
【解决方案4】:

Go 二进制文件很大,因为它们是静态链接的(使用 cgo 的库绑定除外)。尝试静态链接 C 程序,您会看到它增长到相当的大小。

如果这对你来说真的是个问题(我很难相信),你可以用 gccgo 编译并动态链接。

【讨论】:

  • 对于一种尚未被普遍采用的语言来说,这也是一个不错的举措。没有人愿意用 go 系统库来弄乱他们的系统。
  • Go 的创建者明确更喜欢动态链接:harmful.cat-v.org/software/dynamic-linking。 Google 静态链接其所有 C 和 C++:reddit.com/r/golang/comments/tqudb/…
  • 我认为链接的文章说明了相反的情况 - Go 创建者明确更喜欢静态链接。
【解决方案5】:

创建一个名为main.go的文件, 让我们试试简单的 hello world 程序。

package main

import "fmt"

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

我使用 go 版本 1.9.1

$ go version
 go version go1.9.1 linux/amd64

使用标准的go build 命令编译。

$ go build main.go
$ ls -lh
-rwxr-xr-x-x 1 nil nil 1.8M Oct 27 07:47 main

让我们再次使用go build 编译,但使用上面建议的ldflags,

$ go build -ldflags "-s -w" main.go
$ ls -lh
-rwxr-xr-x-x 1 nil nil 1.2M Oct 27 08:15 main

文件大小减少了 30%。

现在,让我们使用gccgo,

$ go version
 go version go1.8.1 gccgo (GCC) 7.2.0 linux/amd64

与gccgo一起构建,

$ go build main.go
$ ls -lh
-rwxr-xr-x 1 nil nil 34K Oct 27 12:18 main

二进制大小几乎减少了 100%。 让我们再次尝试使用gccgo 构建我们的main.go,但使用构建标志,

$ go build -gccgoflags "-s -w" main.go
-rwxr-xr-x 1 nil nil 23K Oct 27 13:02 main

警告: 因为gccgo 二进制文件是动态链接的。 如果你有一个非常大的二进制文件,你用 gccgo 编译的二进制文件不会减少 100%,但会减少相当大的数量。

与 gc 相比,gccgo 编译代码速度较慢,但​​支持更强大的优化,因此 gccgo 构建的 CPU 密集型程序通常会运行得更快。多年来在 GCC 中实现的所有优化都是可用的,包括内联、循环优化、向量化、指令调度等。虽然它并不总是能产生更好的代码,但在某些情况下,使用 gccgo 编译的程序可以运行速度提高 30%。

GCC 7 版本预计将包含 Go 1.8 用户库的完整实现。与早期版本一样,Go 1.8 运行时并未完全合并,但这对 Go 程序不可见。

优点:

  1. 缩小尺寸
  2. 优化。

缺点

  1. 慢
  2. 无法使用最新版本的go。

你可以看到here和here。

【讨论】:

  • 谢谢。我读到了一个问题,gogcc 不支持 ARM 处理器,对吗?而且,你能推荐一个减少 Golang 构建文件大小的工具吗?
  • 你如何用 gccgo 编译?
【解决方案6】:

你应该得到goupx,它将“修复”Golang ELF 可执行文件以与upx 一起工作。在某些情况下,我已经将文件大小缩小了大约 78% ~16MB &gt;&gt; ~3MB。

压缩比通常趋于25%,值得一试:

$ go get github.com/pwaller/goupx
$ go build -o filename
$ goupx filename

>>

2014/12/25 10:10:54 File fixed!

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
  16271132 ->   3647116   22.41%  linux/ElfAMD   filename                            

Packed 1 file.

额外:-s 标志(条带)可以进一步缩小 bin 文件 goupx -s filename

【讨论】:

  • 这太好了,谢谢!我一直在设置 GOARCH=386 并且现在只使用正常的 upx 有一段时间了。
  • 从 Go 1.6 开始不再需要这个,你可以使用普通的 upx。
【解决方案7】:

更紧凑的 hello-world 示例:

package main

func main() {
  print("Hello world!")
}

我们跳过了大的fmt 包并明显减少了二进制文件:

  $ go build hello.go
  $ ls -lh hello
  ... 259K ... hello2
  $ strip hello
  $ ls -lh hello
  ... 162K ... hello2

不像 C 那样紧凑,但虽然用 K 而不是 M 来衡量 :) 好吧,这不是一般的方法,只是展示了一些优化尺寸的方法:使用 strip 并尝试使用最小的包。无论如何,Go 不是用于制作小型二进制文件的语言。

【讨论】:

  • 这段代码使用了内置的 print() 函数,不推荐这种做法。而且问题不在于如何减少提供的示例程序的代码大小,而是以更一般的方式。
  • @wldsvc 我同意你的看法。虽然不明白为什么 print() 是显示输出的坏方法?对于简单的脚本或调试输出来说已经足够了。
  • 请参阅doc.golang.org/ref/spec#Bootstrapping 这些函数已记录在案以确保完整性,但不保证保留在该语言中。对我来说,这意味着在任何脚本中“不要使用它们”,除了一次性调试目的。
  • @wldsvc print() 在 2020 年仍然存在,但具有相同的免责声明
【解决方案8】:

你可以看看我关于这个主题的小研究: https://github.com/xaionaro/documentation/blob/master/golang/reduce-binary-size.md

它逐步展示了如何将 2MiB 的 hello-world 静态二进制样本减少到 15KiB 静态二进制或 10KiB 动态二进制。当然有很多限制。

【讨论】:

    【解决方案9】:

    默认情况下,gcc 链接是动态的,并且是静态的。

    但是如果你静态链接你的 C 代码,你可能会得到一个更大的二进制文件。

    就我而言:

    • go x64 (1.10.3) - 生成大小为 1214208 字节的二进制文件
    • gcc x64 (6.2.0) - 生成大小为 1421312 字节的二进制文件

    两个二进制文件都是静态链接的,没有 debug_info。

    go build -ldflags="-s -w" -o test-go test.go
    gcc -static -s -o test-c test.c
    

    【讨论】:

      【解决方案10】:

      下一个 Go 1.11 的 2018 年答案,as tweetedBrad Fitzpatrick(2018 年 6 月中旬):

      在几分钟前,#golang ELF 二进制文件中的 DWARF 部分现在已被压缩,因此在提示二进制文件现在比 Go 1.10 更小,即使在提示中添加了所有额外的调试内容。

      参照。 Golang issue 11799:

      压缩我们的调试信息可能会带来显着的、廉价的文件大小优势。

      在commit 594eae5中查看更多信息

      cmd/link: 压缩 ELF 二进制文件中的 DWARF 部分

      其中最棘手的部分是二进制布局代码(blk、elfshbits 和其他各种东西)假定符号和节的文件位置及其虚拟地址之间存在恒定偏移。

      压缩当然会破坏这个恒定的偏移量。
      但是我们需要在压缩之前为所有内容分配虚拟地址,以便 在压缩之前解决重定位问题。

      因此,压缩需要根据压缩后的大小重新计算 DWARF 部分和符号的“地址”。
      幸运的是,这些位于文件的末尾,因此不会干扰任何其他部分或符号。 (当然,还有大量的代码假设 DWARF 段排在最后,那么还有什么地方呢?)

      name        old exe-bytes   new exe-bytes   delta
      HelloSize      1.60MB ± 0%     1.05MB ± 0%  -34.39%  (p=0.000 n=30+30)
      CmdGoSize      16.5MB ± 0%     11.3MB ± 0%  -31.76%  (p=0.000 n=30+30)
      [Geo mean]     5.14MB          3.44MB       -33.08%
      

      罗布·派克mentions:

      这只对使用 ELF 的机器有帮助。
      二进制文件仍然太大,而且还在增长。

      布拉德回复:

      至少它是一些东西。即将变得更糟。
      止血一次。

      Reasons: 调试信息,还为 GC 注册映射以让任何指令成为保存点。

      【讨论】:

        【解决方案11】:

        二进制文件默认包含垃圾收集器、管理 go 例程的调度系统以及您导入的所有库。

        结果是大约 1 Mb 的最小大小。

        【讨论】:

          【解决方案12】:

          从 Go 1.8 开始,您还可以使用新的插件系统将二进制文件拆分为类似于共享库的内容。对于此版本,它仅适用于 Linux,但将来可能会支持其他平台。

          https://tip.golang.org/pkg/plugin/

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-10-23
            • 1970-01-01
            • 1970-01-01
            • 2021-11-25
            相关资源
            最近更新 更多