Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

並列コンパイル

2024年11月時点で、 並列フロントエンドは大きな変更の最中にあるため、 このページにはかなり古くなった情報が含まれています。

追跡 issue: https://github.com/rust-lang/rust/issues/113349

2024年11月時点で、Rust コンパイラの大部分は

現在では並列化されています。

  • codegen 部分はデフォルトで並行実行されます。 -C codegen-units=n オプションを使用して、並行タスクの数を制御できます。
  • 型チェック、借用チェック、mir 最適化など、HIR lowering から codegen までの部分は nightly 版で並列化されています。 現在、これらはデフォルトでは直列に実行され、並列化は -Z threads = n オプションを使用してユーザーが手動で有効にします。
  • 字句解析、HIR lowering、マクロ展開などの他の部分は、 まだ直列モードで実行されています。
以下のセクションは現時点では残されていますが、かなり古くなっています。

コード生成

単相化中、コンパイラは生成されるすべてのコードを codegen units と呼ばれるより小さなチャンクに分割します。 これらは並列に実行される LLVM の独立したインスタンスによって生成されます。 最後に、リンカーが実行され、 すべての codegen units を 1 つのバイナリに結合します。 この処理は rustc_codegen_ssa::base モジュールで行われます。

データ構造

並列コンパイラで使用される基盤となるスレッドセーフなデータ構造は、 rustc_data_structures::sync モジュールにあります。 これらのデータ構造は、 parallel-compiler が true かどうかによって異なる方法で実装されています。

データ構造並列非並列
Lock<T>(parking_lot::Mutex<T>)(std::cell::RefCell)
RwLock<T>(parking_lot::RwLock<T>)(std::cell::RefCell)
ReadGuardparking_lot::RwLockReadGuardstd::cell::Ref
MappedReadGuardparking_lot::MappedRwLockReadGuardstd::cell::Ref
WriteGuardparking_lot::RwLockWriteGuardstd::cell::RefMut
MappedWriteGuardparking_lot::MappedRwLockWriteGuardstd::cell::RefMut
LockGuardparking_lot::MutexGuardstd::cell::RefMut
  • これらのスレッドセーフなデータ構造はコンパイル中に散在しており、 ロック競合を引き起こす可能性があり、その結果、スレッド数が 4 を超えて増えると パフォーマンスが低下します。そのため、これらのデータ構造の使用を監査し、 共有状態の使用を減らすためのリファクタリング、または 不変条件、アトミック性、ロック順序の詳細を網羅する永続的なドキュメントの作成につなげています。

  • 一方で、コンパイル中の他のどのような不変条件が 並列コンパイルで成立しない可能性があるのかは、まだ明らかにする必要があります。

WorkerLocal

WorkerLocal は並列コンパイラ向けに実装された特別なデータ構造です。 これはスレッドプール内の各スレッドに対して worker-local な値を保持します。 worker local の値には、 それが構築されたスレッドプール上の Deref impl を通じてのみアクセスできます。 それ以外の場合は panic します。

WorkerLocal は並列環境で Arena アロケータを実装するために使用され、 これは並列クエリにおいて重要です。 その実装は rustc_data_structures::sync::worker_local モジュールにあります。 ただし、 非並列コンパイラでは、これは (OneThread<T>) として実装され、その T には Deref::deref を通じて直接アクセスできます。

並列イテレータ

rayon crate によって提供される並列イテレータは、 並列処理を実装するための簡単な方法です。 並列コンパイラの現在の実装では、 タスクを並列に実行するために 独自に fork した rayon を使用しています。

parallel-compiler が true の場合にループを並列に実行するための いくつかのイテレータ関数が実装されています。

関数(SendSync は省略)概要所有モジュール
par_iter<T: IntoParallelIterator>(t: T) -> T::Iter並列イテレータを生成するrustc_data_structure::sync
par_for_each_in<T: IntoParallelIterator>(t: T, for_each: impl Fn(T::Item))並列イテレータを生成し、各要素に対して for_each を実行するrustc_data_structure::sync
Map::par_body_owners(self, f: impl Fn(LocalDefId))crate 内のすべての hir owner に対して f を実行するrustc_middle::hir::map
Map::par_for_each_module(self, f: impl Fn(LocalDefId))crate 内のすべてのモジュールとサブモジュールに対して f を実行するrustc_middle::hir::map
ModuleItems::par_items(&self, f: impl Fn(ItemId))モジュール内のすべての item に対して f を実行するrustc_middle::hir
ModuleItems::par_trait_items(&self, f: impl Fn(TraitItemId))モジュール内のすべての trait item に対して f を実行するrustc_middle::hir
ModuleItems::par_impl_items(&self, f: impl Fn(ImplItemId))モジュール内のすべての impl item に対して f を実行するrustc_middle::hir
ModuleItems::par_foreign_items(&self, f: impl Fn(ForeignItemId))モジュール内のすべての foreign item に対して f を実行するrustc_middle::hir

コンパイラには、これらの関数を使用して並列化できる可能性のあるループが多数あります。

2022年8月時点で、並列イテレータ関数が使用されているシナリオは次のとおりです。
呼び出し元シナリオ呼び出し先
rustc_metadata::rmeta::encoder::prefetch_mirメタデータのエンコードで後から必要になるクエリをプリフェッチするpar_iter
rustc_monomorphize::collector::collect_crate_mono_items非ジェネリック項目から到達可能な単相化済み項目を収集するpar_for_each_in
rustc_interface::passes::analysismatch 文の妥当性をチェックするMap::par_body_owners
rustc_interface::passes::analysisMIR の borrow checkMap::par_body_owners
rustc_typeck::check::typeck_item_bodies型チェックMap::par_body_owners
rustc_interface::passes::hir_id_validator::check_cratehir の妥当性をチェックするMap::par_for_each_module
rustc_interface::passes::analysisループ本体、属性、naked 関数、不安定な ABI、const 本体の妥当性をチェックするMap::par_for_each_module
rustc_interface::passes::analysisMIR の liveness と intrinsic のチェックMap::par_for_each_module
rustc_interface::passes::analysis到達不能性のチェックMap::par_for_each_module
rustc_interface::passes::analysisプライバシーチェックMap::par_for_each_module
rustc_lint::late::check_crateモジュールごとの lint を実行するMap::par_for_each_module
rustc_typeck::check_cratewell-formedness のチェックMap::par_for_each_module

並列イテレーターを使用できる可能性のあるループは、まだ多数あります。

クエリシステム

クエリモデルには、あまり多くの労力をかけずに複数のクエリを並列に評価することを実際に可能にする性質がいくつかあります。

  • クエリプロバイダーがアクセスできるすべてのデータはクエリコンテキスト経由であるため、 クエリコンテキストがアクセスの同期を処理できます。
  • クエリ結果は不変であることが要求されるため、 異なるスレッドから同時に安全に使用できます。

クエリ foo が評価されると、foo のキャッシュテーブルがロックされます。

  • すでに結果がある場合は、それをクローンし、ロックを解放して 完了します。
  • キャッシュエントリがなく、同じ結果を計算している他のアクティブなクエリ呼び出しもない場合は、 そのキーを「進行中」としてマークし、ロックを解放して 評価を開始します。
  • 同じキーに対する別のクエリ呼び出しが進行中である場合は、 ロックを解放し、待機している結果を別の呼び出しが計算するまで そのスレッドをブロックするだけです。 並列コンパイラにおける循環エラー検出には、 シングルスレッドモードよりも複雑なロジックが必要です。 並列クエリ内のワーカースレッドが相互依存のために進捗しなくなると、 コンパイラは追加のスレッド (deadlock handler という名前) を使用して、 循環エラーを検出、除去、報告します。

並列クエリ機能には、まだ実装すべきことが残っており、その大部分は 前述の Data StructuresParallel Iterators に関連しています。 この未解決の機能追跡 issueを参照してください。

Rustdoc

2022年11月時点では、`rustdoc` のレンダリングを並列化できるようにするまでに

完了すべきステップがまだいくつかあります(並列 rustdoc に関する未解決の議論を参照してください)。

リソース

詳細を学ぶために使用できるリソースをいくつか示します。