mcpField
G
go-binary-size

go-binary-size

github.com/eduardo-sl開発8373 回インストール2 週間前に更新
リポジトリ

go-binary-size について

go-binary-size は、Go バイナリを段階的に小さくし、その都度測定と検証を行う手順です。Go 開発者向けに、リンカー設定、CGO、ビルドタグ、依存関係、埋め込みアセット、コンテナを扱い、削減量と代償の記録を重視します。

主な機能

  1. 01基準サイズと圧縮後サイズの測定
  2. 02パッケージとシンボルのサイズ分析
  3. 03ビルド設定を一つずつ評価
  4. 04CGO、ビルドタグ、依存関係の調査
  5. 05埋め込みアセットとコンテナイメージの評価
  6. 06バイナリとテストの検証

活用シーン

  1. 01Go CLI のリリース成果物の縮小
  2. 02シンボル削除がデバッグに与える影響の評価
  3. 03CGO の有効・無効によるサイズ比較
  4. 04Go アプリケーションのコンテナイメージの縮小

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/eduardo-sl/go-agent-skills --skill go-binary-size

インストール後、Claude Code で/go-binary-sizeを入力して読み込まれたか確認します。

詳細

Go バイナリサイズ

標準の Go バイナリには、ランタイム、ガベージコレクター、完全なシンボルテーブルと行テーブル、さらに推移的に到達可能なすべてのパッケージが含まれます。小規模な CLI でも 8~15 MiB は一般的です。その大部分は削減できますが、測定が欠かせません。どの依存関係が重いかを推測しても、ほとんどの場合は外れます。

手順

変更前と変更後の数値を確認せずに、フラグを適用しないでください。

  1. ベースラインをビルドし、サイズを記録します。
  2. バイト数の内訳を確認します。
  3. 変更の種類を一度に一つずつ適用し、それぞれの後に測定します。
  4. バイナリが引き続き動作し、テストも引き続き合格することを確認します。
  5. 変更 → 削減バイト数 → コストの表を報告します。

1. まず測定する

# Baseline, reproducible
CGO_ENABLED=1 go build -trimpath -o /tmp/base ./cmd/app
ls -l /tmp/base

# Which packages and symbols cost the most
go tool nm -size -sort size /tmp/base | head -40

# Package-level attribution (third-party, more readable)
go install github.com/Zxilly/go-size-analyzer/cmd/gsa@latest
gsa --web /tmp/base

go version -m /tmp/app は、バイナリに組み込まれたモジュール一覧とビルド設定を出力します。リリースで実際にどのフラグが使われたかを確認するのに便利です。

アーティファクトをコンテナーレイヤーやリリースアーカイブで配布する場合は、圧縮後のサイズも測定してください。gzip 後はストリップによる効果が小さくなり、依存関係の削除による効果が大きくなります。

gzip -c /tmp/base | wc -c

2. シンボルと DWARF をストリップする — 単独で最も大きな効果

go build -ldflags="-s -w" -trimpath -o /tmp/stripped ./cmd/app

通常、未圧縮サイズを 25~35%削減できます。

具体的なコストは次のとおりです。

  • ✅ パニックメッセージとゴルーチンのスタックトレースは引き続き機能します。ランタイムは独自の pclntab を使っており、これは -s -w では削除されません。
  • ❌ dlv と gdb でソース行を特定できなくなります。デバッガーを接続する予定の環境には、ストリップ済みのバイナリを出荷しないでください。
  • ⚠️ 外部でシンボルを解決する一部のプロファイリングツールやクラッシュレポートツールの機能が低下します。プロセス内の net/http/pprof には影響しません。

リリースビルドごとに、ストリップしていないコピーを死後解析用に保管してください。

VCS のスタンプが不要な場合は、-buildvcs=false を追加してください。削減効果は小さいですが、配布するアーティファクトからコミットメタデータも取り除けます。

3. インライン化を無効にする — トレードオフを測定する

go build -ldflags="-s -w" -gcflags=all=-l -o /tmp/noinline ./cmd/app

さらに 5~10 パーセントポイント削減できます。ただし、ホットパスの実行時パフォーマンスが低下します。起動して一つの処理を行い、終了する CLI なら許容できるでしょう。レイテンシーに敏感なサーバーでは、まず回帰をベンチマークしない限り許容できません。

4. CGO とランタイム

CGO_ENABLED=0 go build -tags netgo,osusergo -ldflags="-s -w" -o /tmp/pure ./cmd/app

この 3 つはセットで使います。netgo,osusergo を指定せずに cgo を無効にすると、ビルドは C リゾルバーのスタブに依存したままになります。

効果があると思い込む前に確認してください。

  • go list -deps ./... | xargs go list -f '{{.ImportPath}} {{.CgoFiles}}' を実行すると、実際に cgo を使っているパッケージが分かります。
  • cgo を無効にすると、C バインディングの純 Go 版のほうが大きい場合、サイズが増加することがあります。両方を測定してください。
  • リリース設定ですでに CGO_ENABLED=1 または -linkmode=external が指定されている場合は、理由があります。変更する前に理由を調べてください。

cgo を維持する必要があり、プロジェクトが C ソース(SQLite バインディング、画像コーデック)をコンパイルする場合は、CGO_CFLAGS="-Oz" を指定すると、その C コードがサイズ向けに最適化されます。

5. ビルドタグ — 最も見落とされがちな手順

重量級のオプション機能は、通常 Go のソース外にあるタグによって有効化されています。

grep -rn '//go:build' --include='*.go' . | grep -v _test.go
grep -rnE '\-tags' Makefile Taskfile.y*ml .goreleaser.y*ml Dockerfile .github/workflows/ 2>/dev/null

よくある削減方法:使っていないドライバーを除外する、本番ビルドから管理 UI を除外する、実行時にアセットを取得する noembed バリアントをビルドする。

6. 依存関係のサイズ

単一の import がバイナリの大部分を占めることがあります。gsa はモジュールごとのバイト数を割り当てます。直感に頼らず、まずここから調べてください。

よくある原因:

  • クラウドプロバイダーの SDK。個別のサービスパッケージを import し、集約ルートは絶対に import しないでください。
  • github.com/prometheus/client_golang は、少数のカウンターを使うだけでも大きなコード範囲を取り込みます。
  • リフレクションを多用するもの:リンカーは reflect を介したデッドコードの除去ができないため、リフレクションベースのコーデックを使うと、どこからも呼び出されない型が保持されます。
  • 使用していない proto 用の生成済み protobuf パッケージ。

依存関係を標準ライブラリの 40 行のコードに置き換えるのは、正当なサイズ削減です。十分に保守されている依存関係を独自の暗号処理に置き換えるのは、そうではありません。

7. 埋め込みアセット

//go:embed のコンテンツは圧縮されずに保存されます。

//go:embed assets/*
var assets embed.FS

優先順位の高い順に、選択肢は次のとおりです。出荷するアセットを減らす。事前に圧縮し、Content-Encoding: gzip を付けて配信する。バイナリから完全に取り除き、コンテナーイメージまたは CDN に移す。

8. コンテナーイメージ

バイナリは、問題の半分にすぎないことがよくあります。

FROM golang:1.25 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/app ./cmd/app

FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]

scratch は distroless/static より小さいものの、CA 証明書、/etc/passwd、タイムゾーンデータベースが含まれていません。バイナリがこれらを必要としないことを確認済みでない限り、distroless/static を使用してください。

9. UPX — 最終手段。通常は誤りです

upx --best はディスク上のサイズをおよそ半分にしますが、起動のたびに展開のコストがかかり、mmap ベースのツールが動作しなくなり、ウイルス対策ソフトや EDR のヒューリスティック検知を強く誘発します。エンドユーザーに配布するバイナリや、監視対象の本番環境で実行するバイナリは圧縮しないでください。サイズに制約のある組み込み機器向けのターゲットでのみ検討し、その旨をレポートに明記してください。

検証

変更を行うたびに、以下を実行してください。

go build -o /tmp/candidate ./cmd/app && /tmp/candidate --version
go test ./...
ls -l /tmp/base /tmp/candidate

起動しなくなったバイナリや、ビルドタグで有効化されていた機能が失われたバイナリは、小さくなっても成果とはいえません。

検証チェックリスト

  1. フラグを変更する前に、ベースラインのサイズを記録した
  2. 削減効果を主張する項目すべてについて、圧縮前と圧縮後の数値を記録した
  3. -s -w を適用し、デバッグ用にストリップしていない成果物を保持した
  4. レイテンシーに敏感なコードでは、-gcflags=all=-l を想定で済ませず、ベンチマークを実施した
  5. CGO_ENABLED=0 は一方だけでなく両方の方法で測定し、無条件に適用しなかった
  6. Makefile、goreleaser、Dockerfile、CI ワークフローのビルドタグを確認した
  7. 依存関係の分析は、直感ではなくツールを使って行った
  8. 候補のバイナリが実行でき、テストスイートに合格した
  9. UPX は明確な正当化理由がある場合にのみ使用した

コメント