このエントリーをはてなブックマークに追加

Goのバイナリサイズを削減する

Goは1バイナリになってとても良いのですが、その1バイナリが結構大きいのがちょっとネックになる場合もあります。

The Go binary dietという記事を読み、Goのバイナリをダイエットすることにしてみました。

ldflags="-w"

最初に示されていたのは-ldflags="-w"によってDWARFのシンボルテーブルを生成しないようにする方法です。

% go build -ldflags="-w"

ldflags="-s"

次に、-ldflags="-s"によってデバッグのために使われるシンボルテーブルを全部生成しないようにする方法です。

% go build -ldflags="-s"

ここまではよくあるのですが、次に示されていたのはThe Ultimate Packer for eXecutablesを使う方法でした。

UPX

Wikipedia

寡聞にして存じませんでしたが、UPXは1998年からあるソフトウェアのようです。Windows/Linux(i386/ARM/MIPS)/Mac/*BSDとほぼすべての主要プラットフォームで動作します。ライセンスはGPL([注1])です。UPXは実行形式を保ったまま、バイナリを圧縮します。

実際の動作としては、7-Zipなどで使われるLZMAで圧縮しておき、実行時に伸長してメモリ上に直接書き出してから実行します。メモリ上の置き換えが出来ない場合は一時ファイルに書きだしてそれを実行する形式となります。

伸長に必要なコードはわずか数百バイトです。もちろん、伸長時にはCPUやメモリは必要となりますが、一回だけですしGoのバイナリ程度の大きさであれば問題になることはさほどないかと思います。

結果

手元のそこそこ大きいプロジェクトのコードを対象に試してみました。OSはMac OSで、go 1.6を使いました。

対象 バイト数
26MB (27090756)
"-w" 19MB (20414276)
"-s" 26MB (27090756)
"-w" + UPX(-9時) 5.2M (5443584)
"-w" + UPX(-1時) 6.4M (6684672)

あれ、 "-s"では変わってないですね…darwin環境ではでないのかななld周りのなにかだと思うのでそれはあとで追うとして、元々が26MBだったのが、5.2MBまで減りました。

圧縮にupx -9を使った場合、かかった時間は15.70秒でそこそこ時間がかかりますね。3回ほど実行してだいたい同じぐらいでした。伸長時は0.10秒ほどでした。もちろんメモリなどにも依存しますので、この結果は鵜呑みには出来ませんが、あくまで目安として。

さらにいうと、upx -1で圧縮した場合は 0.78秒しかかかりません。それでいて、6.4MBと充分な圧縮効率となりました。この辺りはターゲットとする環境に合わせて決めればいいと思いますが、-1で十分な気もします。

まとめ

Goのバイナリが大きい問題は、ldflagsとUPXを使うことである程度解決できるのではないか、という話でした。UPX知らなかったですけど、コード見るとかなりすごい感じですね。

[注1]ライセンスについて。UPXはGPLに例外事項をつけたライセンスをとっています。これは、GPLであるUPXがバイナリにリンクする形となるため、圧縮対象となるバイナリも通常はGPLになる必要があるのですが、リンクするUPXのコードを改変しないかぎりその必要はない、という例外条項です。ライセンスを確認していただけると分かるように、商用プログラムに対して適用できると明確に書かれています。