strip や gzexe コマンドで Go 言語の実行ファイルを圧縮してみた

Go 言語はシングルバイナリを簡単に生成できるのだが、以下のような Hello World を出力するだけの簡単なファイルでも 1.7MB ほどになったりする。

# こんな Go 言語のソースコードを書いてみた
$ cat ./practice-01.go
package main
import "fmt"
func main() {
  fmt.Printf("hello, world!")
}

# ビルドしてみる
$ go build ./practice-01.go

# ビルドした実行ファイルは 1.7MB 程度になる
$ ls -l ./practice-01
-rwxr-xr-x 1 neo neo 1771121 2022-06-28 01:28 ./practice-01*
$ ls -lh ./practice-01
-rwxr-xr-x 1 neo neo 1.7M 2022-06-28 01:28 ./practice-01*

バイナリファイルってこんなに重たいもんなの?と思ってちょっと調べてみたところ、strip というコマンドを知った。このコマンドは実行ファイル中にあるデバッグ用の情報を削除することで、実行ファイルのサイズを圧縮してくれるようだ。

file コマンドで事前のバイナリファイルを確認すると、not stripped と出力されている。

$ file ./practice-01
./practice-01: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=If9zmA0q8uak25Vtwi9t/M61qxQqNB8bJSYiRbM2T/aAR70uYax_77fI8PV8sU/khQyoJPhKjrEfwhzUIKi, not stripped

それではこのファイルを strip にかけてみる。

# 元のバイナリファイルを上書きする形で圧縮される
$ strip ./practice-01

$ ls -l ./practice-01
-rwxr-xr-x 1 neo neo 1188632 2022-06-28 01:29 ./practice-01*
$ ls -lh ./practice-01
-rwxr-xr-x 1 neo neo 1.2M 2022-06-28 01:29 ./practice-01*

# file コマンドでの結果が stripped に変わっている
$ file ./practice-01
./practice-01: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=If9zmA0q8uak25Vtwi9t/M61qxQqNB8bJSYiRbM2T/aAR70uYax_77fI8PV8sU/khQyoJPhKjrEfwhzUIKi, stripped

すると、1.7MB → 1.2MB ということで、Hello World のみ出力する簡単なプログラムだが、500KB ほどの圧縮ができた。勿論、圧縮した後のファイルも正常に Hello World を出力してくれる。


他に、gzexe というコマンドでもバイナリファイルを圧縮できるようだ。

上述の strip コマンドを使って、圧縮前後で2バージョンの実行ファイルを用意してみた。

$ ls -l ./practice-01-*
-rwxr-xr-x 1 neo neo 1771121 2022-06-28 01:33 practice-01-not-stripped*
-rwxr-xr-x 1 neo neo 1188632 2022-06-28 01:32 practice-01-stripped*
$ ls -lh ./practice-01-*
-rwxr-xr-x 1 neo neo 1.7M 2022-06-28 01:33 practice-01-not-stripped*
-rwxr-xr-x 1 neo neo 1.2M 2022-06-28 01:32 practice-01-stripped*

それぞれのファイルで gzexe コマンドを使ってみる。

$ gzexe ./practice-01-not-stripped
./practice-01-not-stripped:      41.0%
$ gzexe ./practice-01-stripped
./practice-01-stripped:  55.5%

$ ls -l practice-01-*
-rwxr-xr-x 1 neo neo 1045374 2022-06-28 01:33 practice-01-not-stripped*
-rwxr-xr-x 1 neo neo 1771121 2022-06-28 01:33 practice-01-not-stripped~*
-rwxr-xr-x 1 neo neo  530002 2022-06-28 01:34 practice-01-stripped*
-rwxr-xr-x 1 neo neo 1188632 2022-06-28 01:32 practice-01-stripped~*
$ ls -lh practice-01-*
-rwxr-xr-x 1 neo neo 1021K 2022-06-28 01:33 practice-01-not-stripped*
-rwxr-xr-x 1 neo neo  1.7M 2022-06-28 01:33 practice-01-not-stripped~*
-rwxr-xr-x 1 neo neo  518K 2022-06-28 01:34 practice-01-stripped*
-rwxr-xr-x 1 neo neo  1.2M 2022-06-28 01:32 practice-01-stripped~*

すると、末尾にチルダ ~ が付いているファイルが別途生成されている。コチラが元ファイルのバックアップなので、チルダが付いている方は動作確認が済んだら削除してしまっても良い。

圧縮されているファイルを見ると、

という感じで、3分の1から半分近くファイルサイズが削減されている。当然ながら、圧縮後のファイルも正常に Hello World が出力できた。一番最初の 1.7MB のバイナリから比べると、最高で 518KB まで圧縮できるというワケだ。

gzexe コマンドで圧縮したファイルを開いてみると、前半はシェルスクリプトになっており、その場で自身を解凍・展開しながら後半のバイナリデータ部分を実行しているようだ。そのため、実行時に若干パフォーマンス影響がある。Hello World 程度のプログラムではパフォーマンス影響を感じられなかったが、削減できるファイルサイズとのトレードオフで試してみると良いだろう。


こうした圧縮系のツールは他にも色々あるようで、なかなか面白い。