Rustコンパイルの深淵:LTOとcodegen-unitsが支配する「実行速度」と「開発体験」の真実
Rustのコンパイル速度が遅いと嘆くエンジニアは多い。しかし、その「遅さ」の正体は、LLVMが生成するコードの最適化プロセスと、クレートグラフの複雑な依存関係にある。
本稿では、Rustツールチェーンのパフォーマンスを極限まで引き出すための「LTO(Link Time Optimization)」と「codegen-units」の内部機構を解剖し、CI/CD環境においてこれらをどう制御すべきかを詳述する。
—
1. 内部アーキテクチャ:なぜコンパイルは止まらないのか
Rustのコンパイル速度を決定づける主要因は、大きく分けて二つある。
A. Codegen-units: 並列化の諸刃の剣
Rustコンパイラ(rustc)は、クレートを内部で複数の「コード生成ユニット」に分割し、並列処理を行う。
- デフォルト値(16): 中規模プロジェクトでは高速だが、ユニット間で最適化の境界が生まれるため、最終的なバイナリの実行性能は低下する。
- 1に設定した場合: コンパイル時間は劇的に増大する(並列化の恩恵を捨てるため)が、LLVMがクレート全体を俯瞰して最適化できるため、実行バイナリの速度は最大化される。
B. Link Time Optimization (LTO): 究極の最適化
LTOは、静的リンク時に複数のコンパイルユニットをまたいでインライン展開や不要コード削除を行う。
- Fat LTO: クレート単位で全コードをLLVM中間表現(IR)として保持し、リンク時に統合する。メモリ消費が激しいが、最適化の深度は深い。
- Thin LTO: 情報を要約して各ユニットに共有する。Fat LTOに近い性能を出しつつ、コンパイルのオーバーヘッドを大幅に削減できる現代の最適解。
—
2. プロダクト特性に応じた「最適解」の選定基準
開発のフェーズと成果物の種類によって、設定は使い分ける必要がある。
| ターゲット | 目的 | 推奨設定 | 理由 |
| :— | :— | :— | :— |
| 開発環境 | 速度重視 | `codegen-units = 256` | 最適化を捨て、インクリメンタルビルドを最速にする。 |
| ライブラリ | 互換性重視 | `lto = false` | ユーザー側のリンク設定を阻害しないため。 |
| プロダクション | 実行速度重視 | `lto = “thin”`, `codegen-units = 1` | 実行性能とコンパイル時間のベストバランス。 |
—
3. CI/CDパイプラインへの実装:Dockerを用いた「ビルドハック」
CI環境ではメモリ不足によるOOM(Out of Memory)キラーとの戦いになる。`codegen-units = 1` かつ `lto = “thin”` でビルドする場合、rustcは膨大なメモリを消費する。
以下は、Dockerコンテナ内でメモリ制限を考慮しつつパフォーマンスを最大化する `Cargo.toml` のプロファイル設定である。
[profile.release]
本番環境向けの設定
opt-level = 3 # 最大限の最適化
debug = false # バイナリサイズ削減
split-debuginfo = ‘unpacked’ # デバッグ情報を分離し、リンク時間を短縮
lto = “thin” # Thin LTOで最適化と速度のバランスを取る
codegen-units = 1 # 実行時性能を最大化(メモリ消費と引き換え)
panic = “abort” # スタックアンワインドのコードを削除し、バイナリ肥大化を防ぐ
CI/CD(GitHub Actions)でのキャッシュ戦略
Rustのコンパイル成果物は `target/` ディレクトリに集約される。これをキャッシュする際、`codegen-units` の変更はキャッシュのヒット率に直結するため、CI環境では開発環境と統一したプロファイルを使用するのが定石である。
.github/workflows/ci.yml
- name: Build with optimized profiles
run: cargo build –release
env:
# リンク時にメモリを大量消費するため、LLVMの制限を緩和しつつ最適化を制御
RUSTFLAGS: “-C link-arg=-Wl,–compress-debug-sections=zlib”
—
4. 伝説的エンジニアが教える「隠れたハック」
1. `lld` リンカーの導入
デフォルトの `bfd` や `gold` リンカーは遅い。`lld` に切り替えるだけで、特にLTO有効時にはリンク時間が数倍速くなる。
Ubuntu/Debianでのインストール
sudo apt-get install lld
.cargo/config.toml に追記
[target.x86_64-unknown-linux-gnu]
linker = “clang”
rustflags = [“-C”, “link-arg=-fuse-ld=lld”]
2. コンパイル統計の可視化
「どこで止まっているか」を推測で語るのは素人だ。コンパイラの内部統計をJSONで出力し、ボトルネックを特定せよ。
コンパイル時間を詳細に計測する
cargo build –release -Z timings
結果は target/cargo-timings/ 内にHTMLで生成される
—
結び:エンジニアリングの美学
LTOとcodegen-unitsのチューニングは、単なる「速くするための設定」ではない。それは、「バイナリの実行性能(顧客価値)」と「コンパイル時間(エンジニアの生産性)」という、相反するベクトルをアーキテクチャの力で解決する知的作業である。
メモリを喰らい尽くすLLVMを飼いならし、最適化の境界線を制御しきる。その先に、Rustが提供する究極の実行パフォーマンスと、開発者がストレスなくコードをデプロイできるパイプラインが共存する。
さあ、今すぐ `Cargo.toml` を開き、プロジェクトの性格に合わせてコンパイルの深淵を定義し直してほしい。それが、プロフェッショナルの仕事だ。