37DATA

Go编译优化 - 减小 Go 代码编译后的二进制体积

前言

go build使用的是静态编译,会将程序的依赖一起打包,这样一来编译得到的可执行文件可以直接在目标平台运行,无需运行环境(例如 JRE)或动态链接库(例如 DLL)的支持。

虽然 Go 的静态编译很方便,但也存在一个问题:打包生成的可执行文件体积较大,毕竟相关的依赖都被打包进来了,尤其是在测试环境中。为此,可以尝试一下压缩 Go 编译得到的可执行文件的体积。

1. 添加 -ldflags 参数

在程序编译的时候可以加上-ldflags "-s -w"参数来优化编译,原理是通过去除部分链接和调试等信息来减小编译生成的可执行程序体积,具体参数如下:

  • -a:强制编译所有依赖包

  • -s:忽略符号表和调试信息,不过panic的时候stace trace就没有任何文件名/行号信息了

  • -w:忽略DWARFv3调试信息,不过得到的程序就不能使用gdb进行调试了

提示:不建议-w和-s同时使用

2. 编译优化示例

未添加编译参数,main原体积约为19.6M:

> go build main.go # 直接编译
> ls -alh main # 约为 19.6M-rwxr-xr-x 1 lizhihao staff 19.6M 6 25 15:52 main

添加-w参数,去掉调试信息,体积减小至约15.8M:

> go build -ldflags "-w" main.go # 添加 -w,去掉调试信息
> ls -al main # 约为 15.8M-rwxr-xr-x 1 lizhihao staff 15.8M 6 25 15:54 main

添加-s参数,去掉符号表,体积减小至约14.7M:

> go build -ldflags "-s" main.go # 添加 -s,去掉符号表
> ls -alh main # 约为 14.7M-rwxr-xr-x 1 lizhihao staff 14.7M 6 25 15:58 main

同时添加-w -s参数,体积同样约为14.7M:

> go build -ldflags "-w -s" main.go # 同时添加 -w -s
> ls -alh main # 同样约为 14.7M-rwxr-xr-x 1 lizhihao staff 14.7M 6 25 16:05 main

可以看到添加-s参数时,可执行文件体积减小最多

若对符号表无需求,-ldflags直接添加"-s"即可

3. 使用 upx

UPX - the Ultimate Packer for eXecutables 是一款开源的可执行文件压缩程序,可以压缩常见平台下的可执行程序包,通常可减少 50-70% 的体积。

UPX is an advanced executable file compressor. UPX will typically reduce the file size of programs and DLLs by around 50%-70%, thus reducing disk space, network load times, download times and other distribution and storage costs.

3.1 安装 upx

upx 的安装方式非常简单,我们可以直接从 github 下载最新的 release 版本,支持 Windows 和 Linux,在 Ubuntu 或 Mac 可以直接使用包管理工具安装:

# For MacOS> brew install upx
# For CentOS/Fedora/RHEL> yum install upx
> yum info upxInstalled PackagesName : upxArch : x86_64Version : 3.95Release : 4.el7Size : 1.8 MRepo : installedFrom repo : epelSummary : Ultimate Packer for eXecutablesURL : http://upx.sourceforge.net/License : GPLv2+ and Public DomainDescription : UPX is a free, portable, extendable, high-performance executable : packer for several different executable formats. It achieves an : excellent compression ratio and offers very fast decompression. Your : executables suffer no memory overhead or other drawbacks.

3.2 直接编译后压缩

直接go build后压缩,main.exe体积从19.6M压缩至9.1M,为原体积的46.48%:

> go build main.go
> ls -alh main # 约为 19.6M-rwxr-xr-x 1 lizhihao staff 19.6M 6 25 16:32 main

> upx main # 压缩至原体积的 46.48% Ultimate Packer for eXecutables Copyright (C) 1996 - 2018UPX 3.95w Markus Oberhumer, Laszlo Molnar & John Reiser Aug 26th 2018
File size Ratio Format Name -------------------- ------ ----------- ----------- 20556800 -> 9554944 46.48% win64/pe main.exe
Packed 1 file.
> ls -alh main # 约为 9.1M-rwxr-xr-x 1 lizhihao staff 9.1M 6 25 16:34 main

3.3 编译优化后压缩

添加-ldflags "-s"后压缩,main体积从14.7M压缩至4.8M,为原体积的32.42%:

> go build -ldflags "-s" main.go
> ls -alh main # 约为 14.7M-rwxr-xr-x 1 lizhihao staff 14.7M 6 25 16:38 main
> upx main # 压缩至原体积的 32.42% Ultimate Packer for eXecutables Copyright (C) 1996 - 2018UPX 3.95w Markus Oberhumer, Laszlo Molnar & John Reiser Aug 26th 2018
File size Ratio Format Name -------------------- ------ ----------- ----------- 15397888 -> 4992512 32.42% win64/pe main.exe
Packed 1 file.
> ls -alh main # 约为 4.8M-rwxr-xr-x 1 lizhihao staff 4.8M 6 25 16:38 main

最终经过-ldflags编译优化和upx压缩,main.exe体积从19.6M压缩至4.8M,约为原体积的24.5%,效果拔群。

3.4 交叉编译后压缩

另外,upx不仅可以压缩当前主机平台的可执行程序,只要是支持的平台都可以进行压缩。因此可以先在本机进行交叉编译,之后使用上述方法压缩可执行程序,最后再将可执行程序传至目标平台运行。

以在windows/amd64下交叉编译linux/amd64为例,main体积从19.9M压缩至4.8M,约为原体积的24.2%,压缩比例大致相同:

# 设置交叉编译环境变量> set GOOS=Linux> set GOARCH=amd64> set CGO_ENABLED=0
# 直接交叉编译> go build main.go
> ls -alh main # 约为 19.9M-rwxr-xr-x 1 lizhihao staff 19.9M 6 25 16:38 main
# 开启编译优化,去掉符号表> go build -ldflags "-s" main.go
> ls -alh main # 约为 14.7M-rwxr-xr-x 1 lizhihao staff 14.7 6 25 16:40 main
> upx main # 压缩至原体积的 32.60% Ultimate Packer for eXecutables Copyright (C) 1996 - 2018UPX 3.95w Markus Oberhumer, Laszlo Molnar & John Reiser Aug 26th 2018
File size Ratio Format Name -------------------- ------ ----------- ----------- 15464736 -> 5041696 32.60% linux/amd64 main
Packed 1 file.
> ls -alh main # 约为 4.8M-rwxr-xr-x 1 lizhihao staff 4.8M 6 25 16:50 main

3.5 upx 和编译选项组合

组合命令

$ go build -ldflags="-s -w" -o server main.go && upx -9 server        File size         Ratio      Format      Name   --------------------   ------   -----------   -----------   8213876 ->   3170320   38.60%   macho/amd64   server $ ls -lh server-rwxr-xr-x  1 lizhihao staff   3.0M Dec  8 00:47 server

下面是upx的一些常用参数:

  • -o:指定输出的文件名

  • -k:保留备份原文件

  • -1:最快压缩,共1-9九个级别

  • -9:最优压缩,与上面对应

  • -d:解压缩decompress,恢复原体积

  • -l:显示压缩文件的详情,例如upx -l main.exe

  • -t:测试压缩文件,例如upx -t main.exe

  • -q:静默压缩be quiet

  • -v:显示压缩细节be verbose

  • -f:强制压缩

  • -V:显示版本号

  • -h:显示帮助信息

  • --brute:尝试所有可用的压缩方法,slow

  • --ultra-brute:比楼上更极端,very slow

3.6 upx 的原理

upx 压缩后的程序和压缩前的程序一样,无需解压仍然能够正常地运行,这种压缩方法称之为带壳压缩,压缩包含两个部分:

  • 在程序开头或其他合适的地方插入解压代码;

  • 将程序的其他部分压缩。

执行时,也包含两个部分:

  • 首先执行的是程序开头的插入的解压代码,将原来的程序在内存中解压出来;

  • 再执行解压后的程序。

也就是说,upx 在程序执行时,会有额外的解压动作,不过这个耗时几乎可以忽略。同时使用upx后内存占用实测增加40%。

4、结论
如果对编译后的体积没什么要求的情况下,可以不使用 upx 来压缩。
一般在服务器端独立运行的后台服务,无需压缩体积。
建议可以本地和测试环境使用,增加编译速度,提高效率。