Cargoビルドプロセスの深掘り—依存関係の可視化

Rustプロジェクトのビルド時間は、開発効率に直結する重要な要素です。今回の記事では、筆者が約10年のスケジューラ開発経験を持つエンジニアとして、Cargoのスケジューリングメカニズムを外部から分析し、改善の余地を検証しています(出典)。

筆者は、15の著名なRustプロジェクトと自身が開発するHyperQueue、FairyFlowの計17プロジェクトをベンチマークとして使用。cargo build --timingsでは得られない詳細な依存関係グラフを、syscallトレースを用いて再構築しています。これは、各rustcプロセスがどのファイルを読み書きし、どのような順序で実行されるかを把握するためのものです。

クレートのコンパイル開始には、依存するクレートの"メタデータ"(.rmetaファイル)のみが必要であり、コンパイル完了を待つ必要はないという点が重要な発見です。

この分析により、Rustのクレートコンパイルが「メタデータ生成(frontend)」と「残りのコンパイル(rest)」の2段階に分けられることが判明しました。依存クレートはfrontendの完了を待つだけでよく、restはfrontendの直後に同じワーカーで強制的に実行されるという構造が示されています。

これは、Rustのビルドシステムを深く理解する上で非常に興味深い点だと感じます。私自身も普段TypeScriptやPythonを使っていますが、コンパイル言語ならではの複雑な依存関係解決の工夫が垣間見えます。

b-levelスケジューラの提案とパフォーマンス分析

筆者は、Cargoのデフォルトスケジューリング(以下「cargo」)をベースラインとして、b-levelスケジューラという新しいアプローチを提案しています。b-levelとは、各タスクの後に実行されるタスクチェーンの最長時間を指し、これが長いタスクほど優先的に実行されるように設計されています。実験は、ノートPCのコア数であるn=16と、より制約のあるCI環境を想定したn=4の並列度で行われました。

Cargoとb-levelスケジューラのパフォーマンス比較を以下に示します(記事のグラフからの定性的な情報に基づく):

スケジューラ n=16 (高性能PC相当) n=4 (CI環境相当)
Cargo (ベースライン) 基準 基準
b-levelスケジューラ Cargoと同等か若干改善 Cargoより顕著に改善

特にn=4のような並列度が低い環境では、b-levelスケジューラがCargoよりも優れたビルド時間短縮効果を示しているとのことです。これは、リソースが限られたCI/CDパイプラインにおいて、ビルド時間を大幅に短縮できる可能性を秘めていることを意味します。

私の個人プロダクトのCIでもビルド時間がボトルネックになることがあるので、これはぜひ試してみたいですね。もしCargoにPythonバインディングでもあれば、既存のスクリプトに組み込んで試してみたいと感じます。

他のRustプロジェクト動向と今後の展望

今回のスケジューラ改善の提案は、Rustエコシステム全体の生産性向上に貢献するものです。また、Rustの機能拡張の動きも活発です。例えば、RustプロジェクトはRust FoundationのRust-C++ Interop Initiativeと連携し、FFIバインディングのための関数オーバーロードを実験中です(出典)。

これは、C++コードをRustからより人間工学的に呼び出すことを目指しており、Stable Rustが既にサポートしているタプルとトレイトによるオーバーロードの不便さを解消しようとするものです。このようなRust言語自体の利便性向上も、開発者の生産性を高める上で重要でしょう。

これらの動向は、Rustが単なる言語機能だけでなく、ビルドシステムやツールチェインの改善にも力を入れていることを示しています。ローカル環境での開発からCI/CDまで、よりスムーズな開発体験を提供しようとするコミュニティの努力が感じられます。特に、b-levelスケジューラのような理論に基づいた改善案が出てくるのは、成熟したエコシステムならではの動きではないでしょうか。

ビルド時間の最適化は、大規模プロジェクトや頻繁なCI実行において、コストと時間の両面で大きなインパクトをもたらします。

クラウド上で多数のRustプロジェクトをビルドしている企業にとって、このようなスケジューラの改善はインフラコストの削減にも直結する可能性があります。GCPのCloud RunやCloud BuildでRustプロジェクトを動かしているエンジニアの方にとっては、特に注目すべき情報だと感じます。