並行プログラムでは、プログラマは、異なるプロセスまたはスレッド間のデータの同期に特別な注意を払い、特に複数のスレッドが同じ変数を同時に変更する場合、データが正しく変更されることを保証するために、信頼性の高い同期またはその他の措置を講じる必要があります。ここでのポイントは、命令が実行される順序を想定しないことです。異なるスレッド間の命令が実行される順序は予測できません。
しかし、シングルスレッド プログラムでは、通常、命令が順番に実行されると簡単に想定できます。そうでない場合、プログラムにどのようなひどい変化が起こるか想像できます。理想的なモデルは、さまざまな命令が実行される順序が一意であり、その順序が、プロセッサーやその他の要因に関係なく、コードに記述された順序であるということです。このモデルは、逐次一貫性モデルと呼ばれます。フォン・ノイマンシステムに基づいたモデルです。もちろん、この仮定自体は合理的であり、実際には異常が発生することはほとんどありません。しかし、実際には、このモデルは非効率すぎるため、現代のマルチプロセッサ アーキテクチャはこのモデルを採用していません。コンパイルの最適化と CPU パイプラインでは、ほとんどすべてに命令の並べ替えが含まれます。
コンパイル時の並べ替え
一般的なコンパイル時の順序変更は、プログラムのセマンティクスを変更せずにレジスタの読み取りと格納の数をできる限り減らし、レジスタに格納された値を完全に再利用するように命令の順序を調整することです。
最初の命令が値を計算して変数 A に代入し、それをレジスタに格納するとします。2 番目の命令は A とは関係ありませんが、レジスタを占有する必要があります (A が配置されているレジスタを占有すると仮定します)。命令は A の値を使用し、2 番目の命令とは関係がありません。次に、逐次一貫性モデルに従って、最初の命令が実行された後に A がレジスタに入れられ、2 番目の命令が実行されると A が存在しなくなり、3 番目の命令が実行されると A が再びレジスタに読み込まれるとします。このプロセスでは、A の値は変化しません。通常、コンパイラは 2 番目と 3 番目の命令の位置を交換して、最初の命令の終わりに A がレジスタに存在するようにします。その後、A の値をレジスタから直接読み取ることができるため、繰り返し読み取るオーバーヘッドが軽減されます。
パイプラインの再順序付けの重要性
最近の CPU はほとんどすべて、命令の処理を高速化するためにパイプライン メカニズムを使用しています。一般的に、命令の処理には複数の CPU クロック サイクルが必要ですが、パイプラインの並列実行により、複数の命令を同じクロック サイクルで実行できます。方法を簡単に説明します。読み取り、アドレス指定、解析、実行などの実行サイクルは、異なるコンポーネントで処理されます。同時に、実行ユニット EU では、機能ユニットが加算コンポーネント、乗算コンポーネントなどの異なるコンポーネントに分割されます。 、コンポーネント、ストレージ要素などをロードすることで、さまざまな計算の並列実行をさらに実現できます。
パイプライン アーキテクチャでは、命令はシーケンシャル モデルで考慮されるのではなく、並列で実行される必要があります。並べ替えはパイプラインを最大限に活用するのに役立ち、それによってスーパースカラー効果を実現します。
秩序を確保する
命令は必ずしも記述した順序で実行されるわけではありませんが、シングルスレッド環境では、命令実行の最終的な効果が逐次実行での効果と一致している必要があることに疑いの余地はありません。そうしないと、この最適化の意味が失われます。
通常、命令の並べ替えがコンパイル時に実行されるか実行時に実行されるかに関係なく、上記の原則が満たされます。
Javaストレージモデルでの並べ替え
Java メモリ モデル (JMM) では、特に同時プログラミングにおいて、並べ替えは非常に重要なセクションです。 JMM は、操作 B を実行するスレッドが操作 A を実行する結果を監視するようにするには、事前発生ルールによって順次実行セマンティクスを保証します。そうでない場合、JVM は任意の実行を行うことができます。プログラムのパフォーマンスを向上させるための並べ替え操作。
volatile キーワードを使用すると、変数の可視性を確保できます。これは、volatile に対する操作がすべてメイン メモリ内で行われ、メイン メモリがすべてのスレッドで共有されるためです。その代償として、パフォーマンスが犠牲になり、レジスタやキャッシュはグローバルではないため使用できなくなります。 、可視性は保証できず、ダーティリードが発生する可能性があります。
volatile のもう 1 つの機能は、再順序付けをローカルで防止することです。再順序付けすると、可視性の問題が発生する可能性があるため、volatile 変数の操作命令は再順序付けされません。
可視性の確保に関しては、ロック (明示的ロック、オブジェクト ロックを含む) およびアトミック変数の読み書きによって変数の可視性を確保できます。ただし、実装方法は若干異なります。たとえば、同期ロックでは、ロックが解放されると、データがメモリに書き戻されてキャッシュが更新されます。データは可視ですが、揮発性変数は単にメモリを読み書きするだけです。