バイナリサイズ。それはRustエンジニアが最後に行き着く、最も知的でシビアな戦場です。
「Rustで書いたから速いし安全だ」。それは事実です。しかし、「Rustで書いたからバイナリが肥大化しやすい」というのもまた、避けては通れない事実です。特に、エコシステムの利便性を享受するために依存クレートを多用すればするほど、バイナリは知らぬ間に膨れ上がり、配布コストやコールドスタートの速度に悪影響を及ぼします。
今回は、その「バイナリの肥大化」というブラックボックスを解体し、あなたのRustアプリケーションを極限までシェイプアップする「cargo-bloat」という最強の武器の使い方を伝授します。
—
なぜ「バイナリサイズ」が開発効率と直結するのか
バイナリが小さいことは、単にディスク容量を節約する以上の意味を持ちます。
- CI/CDパイプラインの高速化: アーティファクトの転送時間が減る。
- コンテナイメージの軽量化: 起動時のプル時間が劇的に短縮される。
- メモリ効率の向上: 命令キャッシュ(I-cache)のヒット率が向上し、実行速度そのものが改善する。
「動けばいい」という段階を卒業し、プロフェッショナルなレベルでシステムを構築するなら、バイナリの構成要素を可視化するのは必須の教養です。
—
1. cargo-bloat:バイナリの「健康診断」ツール
`cargo-bloat`は、単にバイナリ全体のサイズを測るツールではありません。コンパイルされたバイナリを静的解析し、「どの関数が、どのクレートから持ち込まれ、どれだけのバイト数を占有しているか」をランキング形式で抽出する、いわばバイナリのMRI診断ツールです。
インストールとセットアップ
まずは、Cargoのプラグインとしてインストールします。Rustツールチェーンが整っていれば、一瞬で完了します。
cargoのプラグインとしてインストール
cargo install cargo-bloat
インストールが成功したか確認
cargo bloat –version
動作確認:まずは「今のサイズ」を知る
手元のプロジェクトで以下のコマンドを実行してください。
リリースビルドで分析を行う(最適化オプションが効いた状態を測定するため)
cargo bloat –release –crates
このコマンドを叩くと、コンソールには依存関係ごとのサイズがズラリと表示されます。ここで見るべきポイントは、「自分がコードを書いていないのに、なぜか巨大な容量を食っているクレート」です。
—
2. 「戦犯」を特定し、ダイエットを成功させるフロー
ここからが本題です。肥大化したバイナリを小さくする具体的なステップを解説します。
ステップ1:不要なフィーチャーを削ぎ落とす
多くのRustクレートには `default-features = true` が設定されており、あなたが使っていない機能までバイナリにリンクされています。
例:`serde`や`tokio`など
例えば、`tokio`を使っている場合、デフォルトでは不要な機能(`macros`や`rt-multi-thread`など)が含まれていることがあります。`Cargo.toml`で以下のように制御しましょう。
[dependencies]
defaultをfalseにし、必要な機能だけを明示的に指定する
tokio = { version = “1.0”, default-features = false, features = [“rt”, “macros”] }
ステップ2:panicの挙動を見直す
Rustはデフォルトでパニック時に詳細なエラーメッセージを保持しようとしますが、これが意外とバイナリを肥大化させます。`Cargo.toml`に以下の設定を追加するだけで、数KB〜数百KB単位で軽量化できる場合があります。
[profile.release]
パニック時にスタックトレースを生成せず、即座に終了させる(サイズ削減)
panic = “abort”
リンク時の最適化を有効にする
lto = true
コード生成の単位を調整し、最適化の余地を増やす
codegen-units = 1
—
3. 現場で役立つ「深い」分析テクニック
もし特定の関数が異常に大きいことが判明した場合、それは「ジェネリクスの単相化(Monomorphization)」が原因である可能性が高いです。
`cargo-bloat`で以下のフラグを追加してみましょう。
関数レベルの詳細なサイズを表示
cargo bloat –release –functions
ここで、同じような名前の関数が大量に並んでいたら注意が必要です。Rustはジェネリクスを使用する際、型ごとに個別のコードを生成します。これがコードの重複を生み、バイナリを肥大化させます。
対策案:
- 頻出するロジックはジェネリクスを避け、トレイトオブジェクト(`dyn Trait`)への切り替えを検討する(※パフォーマンスとのトレードオフになります)。
- インライン化(`#[inline]`)しすぎない。インライン化は速度を上げますが、コードがコピーされるためバイナリは大きくなります。
—
最後に:完璧を目指さず、継続的に測定する
バイナリサイズ削減の旅に終わりはありません。重要なのは、「何が肥大化の要因かを知っている」という状態を保つことです。
今日から、CIパイプラインの最後に `cargo-bloat` の結果をログ出力するように設定してみてください。あるいは、サイズが一定値を超えたらエラーを出すようなスクリプトを組むのも非常にプロフェッショナルです。
「なぜこの関数がこのサイズなのか?」という問いを繰り返すたび、あなたはRustのコンパイラとメモリの構造をより深く理解していくことになります。その知見こそが、あなたのエンジニアとしての価値を次のレベルへ押し上げるはずです。
さあ、あなたのプロジェクトをスリムにして、より鋭く、より高速なコードを世界へ届けましょう。応援しています!