はじめに
GoのBuildオプションでサイズに影響しそうなオプションで、どの程度サイズに差がでるか試しました。
以下の環境です。自分のよくあるGoのユースケースとしてWebAPI開発あるので、go-swaggerやAWS SDK Goに依存したプロジェクトで試してみました。
$cat /etc/os-release | head -n2
NAME="Ubuntu"
VERSION="18.04.4 LTS (Bionic Beaver)"
$ go version
go version go1.14.2 linux/amd64
比較
# 1. ノーオプション
go build -o nooption main.go
# 2. ldflagsでdebug情報を削除
go build -ldflags="-s -w" -o ldflags main.go
# 3. trimpathでパス情報を削除
go build -ldflags="-s -w" -trimpath -o trimpath main.go
-
-ldflags
-
Go Documentによるとリンカーへの引数。
-s
,-w
はそれぞれシンボルテーブルとデバック情報とのこと。
-
Go Documentによるとリンカーへの引数。
-
-trimpath
- Go1.13から追加されたオプション。バイナリから全ファイルシステムパスを削除する
- panic で表示されるパスからBuild時のディレクトリ構造を消すためにも指定
UPX
Goのバイナリサイズを削減する で初めて知ったのですが、UPXは実行形式を保ったまま、バイナリを圧縮するツールとのことです。(ライセンスは記事にかかれている通り、GPLに例外事項をつけたライセンスですので、本番環境への利用時は念のため確認してください)
基本的には、UPXはバイナリを圧縮するツールですが、実行ファイル圧縮 形式で行ってくれるらしく、元のバイナリの圧縮+自己展開のバイナリも付与されるモデルだそうです。そのため、バイナリサイズが小さくなるトレードオフとして、(どの程度かはさておき)起動時に圧縮されたバイナリを展開するため起動速度は遅くなるようです。一度起動してしまえば後は性能差は無いと思います。
UbuntuにUPXをインストールします。
$sudo apt-get update -y
$sudo apt-get install -y upx
# Versionの確認
$upx --version | head -4
upx 3.94
UCL data compression library 1.03
zlib data compression library 1.2.11
LZMA SDK version 4.43
UPXを実行してみます。
https://linux.die.net/man/1/upx によると、-1から-9まで圧縮レベルを指定できるそうです。 --best
で最善を尽くしてくれるそうです。ちなみにデフォルトは -8 だそうです。
upx -1 -o upx1 trimpath
upx -8 -o upx8 trimpath
upx -9 -o upx9 trimpath
upx --best -o upx_best trimpath
-8
より上のレベルはかなりパックに時間がかかったので、リモートサーバならともかく、ローカル環境で毎Build時にやることはオススメしないなって思いました。特に--best
は数分はかかった気がします。
--lzma
で LZMA(Lempel-Ziv-Markov chain-Algorithm) を有効にできるとのこと。
upx --lzma -o upx_lzma trimpath
--brute
, --ultra-brute
のオプションもあるようなので試してみます。brute
は man
で確認すると..
Compression tuning options:
--brute try all available compression methods & filters [slow]
--ultra-brute try even more compression variants [very slow]
...とのことで、良い圧縮率が期待できそうです。
upx --brute -o brute trimpath
upx --ultra-brute -o ultra_brute trimpath
これも --best
と同様にかなりパック時間がかかりました。
サイズ
No | Name | Size [KB] | 1と比べたときのサイズ |
---|---|---|---|
1 | nooption | 22,501 | 100% |
2 | ldflags | 16,996 | 75.5% |
3 | trimpath | 16,968 | 75.4% |
4 | 3 + UPX 1 | 6,622 | 29.4% |
5 | 3 + UPX 8 | 5,635 | 25.0% |
6 | 3 + UPX 9 | 5,564 | 24.7% |
7 | 3 + UPX BEST | 5,522 | 24.5% |
8 | 3 + UPX LZMA | 4232 | 18.9% |
9 | 3 + UPX BRUTE | 4218 | 18.7% |
10 | 3 + UPX ULTRA BRUTE | 4198 | 18.7% |
2,3はほぼ変わらないですが、ファイルシステムパスを削除した分、多少サイズが減っています。(30KBなので小さいシステムだとインパクト大きいかもしれません)
4以降のUPXが劇的過ぎてビビるですが、試しにパックしたバイナリを実行してみるともちろん普通に起動しました。パックするにはオプションによってそこそこ処理時間がかかります。圧縮率と処理時間のバランスで、UPXを使うのであれば --lzma
が1番バランスが良さそうだなと思いました。 BRUTEは時間がかかるので。
再現性のあるBuildをするためには
https://iguchitomokatsu.com/posts/how-to-make-static-binary-of-golang/
を読んで知ったのですが、 -ldflags=-buildid=
を足すと完全に再現性のあるバイナリが生成できるそうです。
go build -ldflags="-s -w -ldflags=-buildid=" -trimpath -o trimpath main.go
まとめ
- ライブラリ提供でなく実行バイナリをビルドするのであれば、
-ldflags="-s -w"
や-trimpath
をつけるのがベターで、そこそこ依存関係があるPJでも25%ほどバイナリサイズを小さくすることができた - UPXの効果が素晴らしい