通常、大規模なソフトウェアプロジェクトは、比較的独立してコンパイルできる多数のコンパイル単位で構成されているため、コンパイラを並列に数回呼び出すことにより、非常に大まかな粒度でコンパイルが並列化されることがよくあります。これはOSプロセスのレベルで発生し、適切なコンパイラーではなくビルドシステムによって調整されます。私はこれがあなたが尋ねたものではないことを理解していますが、それはほとんどのコンパイラの並列化に最も近いものです。
何故ですか?さて、コンパイラが行う作業の多くは、簡単には並列化に役立ちません。
- 入力をいくつかのチャンクに分割して、それらを個別にlexすることはできません。簡単にするために、lexmeの境界で分割したいので(lexmeの途中でスレッドが開始されないように)、lexmeの境界を決定するには多くのコンテキストが必要になる可能性があります。たとえば、ファイルの途中でジャンプする場合は、文字列リテラルにジャンプしていないことを確認する必要があります。しかし、これを確認するには、基本的に以前に来たすべてのキャラクターを調べる必要があります。その上、現代言語用のコンパイラーでは、字句解析がボトルネックになることはめったにありません。
- 解析は並列化がさらに困難です。字句解析のために入力テキストを分割する問題はすべて、解析のためにトークンを分割することにさらに適用されます。たとえば、関数の開始位置を決定することは、基本的に関数の内容を解析するのと同じくらい難しいです。これを回避する方法もあるかもしれませんが、それらはおそらくほとんど利益をもたらさないほど不均衡に複雑になります。解析も最大のボトルネックではありません。
- 解析した後、通常は名前解決を実行する必要がありますが、これは関係の巨大な織り込みネットにつながります。ここでメソッド呼び出しを解決するには、最初にこのモジュールのインポートを解決する必要がありますが、別のコンパイル単位などで名前を解決する必要があります。言語にそれがある場合は型推論についても同じです。
この後、少し簡単になります。型チェックと最適化、およびコード生成は、原則として、関数の粒度で並列化される場合があります。おそらく、これほど大規模なタスクを同時に実行することは非常に難しいため、これを行うコンパイラーはほとんどいません。また、ほとんどの大規模なソフトウェアプロジェクトには非常に多くのコンパイルユニットが含まれているため、すべてのコア(場合によってはサーバーファーム全体)を占有するために「多数のコンパイラを並列実行」する方法で十分です。さらに、大規模なコンパイルタスクでは、ディスクI / Oが実際のコンパイル作業と同じくらいボトルネックになる可能性があります。
とはいえ、コード生成と最適化の作業を並列化するコンパイラーは知っています。Rustコンパイラーは、バックエンド作業(LLVM、実際には従来「ミドルエンド」と考えられていたコード最適化を含む)を複数のスレッドに分割できます。これは「コード生成単位」と呼ばれます。上記の他の並列化の可能性とは対照的に、これは次の理由で経済的です。
- この言語には(たとえばCやJavaと比較して)かなり大きなコンパイル単位があるため、飛行中のコンパイル単位はコアよりも少ない可能性があります。
- 並列化されている部分は通常、コンパイル時間の大部分を占めます。
- バックエンドの作業は、ほとんどの場合、恥ずかしいほど並行しています。最適化して、各機能を独立してマシンコードに変換するだけです。もちろん、プロシージャ間の最適化があり、codegenユニットはそれらを妨げ、パフォーマンスに影響を与えますが、セマンティックの問題はありません。