LoginSignup
19
14

More than 3 years have passed since last update.

よく使うGoのBuildオプションと、UPXの圧縮がバイナリサイズにどの程度影響しているか調べた

Last updated at Posted at 2020-04-14

はじめに

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
  • -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 は数分はかかった気がします。

--lzmaLZMA(Lempel-Ziv-Markov chain-Algorithm) を有効にできるとのこと。

LZMA利用
upx --lzma -o upx_lzma trimpath 

--brute, --ultra-bruteのオプションもあるようなので試してみます。bruteman で確認すると..

Compression tuning options:
--brute try all available compression methods & filters [slow]
--ultra-brute try even more compression variants [very slow]

...とのことで、良い圧縮率が期待できそうです。

brute利用
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= を足すと完全に再現性のあるバイナリが生成できるそうです。

build例
go build -ldflags="-s -w -ldflags=-buildid=" -trimpath -o trimpath main.go

まとめ

  • ライブラリ提供でなく実行バイナリをビルドするのであれば、-ldflags="-s -w"-trimpath をつけるのがベターで、そこそこ依存関係があるPJでも25%ほどバイナリサイズを小さくすることができた
  • UPXの効果が素晴らしい
19
14
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
14