CPUがプログラムのパフォーマンスを最適化するために多くのことを行っていることがわかります
この記事では、CPUがプログラムのパフォーマンスを最適化するために行った取り組みを理解できるように、メモリバリアとCPUキャッシュの知識の学習に焦点を当てています。
まず、CPUキャッシュを確認します。
CPUキャッシュ
CPUキャッシュは、プログラムのパフォーマンスを向上させるために使用されます。CPUは、CPUキャッシュなど、多くのプロセスの内部アーキテクチャに多くの調整を加えました。ハードディスクは非常に遅いため、キャッシュを介してメモリにデータをロードしてアクセス速度を上げることができることは誰でも知っています。 CPU処理にもこのメカニズムがあります。可能であれば、メインメモリーにアクセスするプロセッサーの時間オーバーヘッドをCPUキャッシュに入れます。CPUアクセス速度は、メモリーアクセス速度より何倍も高速です。これは、ほとんどのプロセッサーが現在使用しているメカニズムです。プロセッサーのキャッシュを使用してパフォーマンスを向上させます。
マルチレベルキャッシュ
CPUのキャッシュは3つのレベルのキャッシュに分割されているため、マルチコアCPUには複数のキャッシュがあるため、最初に次のレベルのキャッシュ(L1キャッシュ)を調べます。
L1キャッシュは、データキャッシュと命令キャッシュに分かれたCPUの1次キャッシュであり、一般的なサーバーCPUのL1キャッシュの容量は、通常32〜4096 KBです。
L1キャッシュ容量の制限により、CPUの動作速度を再び上げるために、高速メモリがCPUの外部、つまりL2キャッシュに配置されます。
L1とL2の容量はまだ限られているため、3レベルのキャッシュが提案され、L3が組み込まれました。実際の役割は、L3キャッシュを適用すると、メモリレイテンシをさらに削減し、大量のデータを計算するときにプロセッサを改善できることです。より大きなL3キャッシュを備えたプロセッサーのパフォーマンスは、より効率的なファイルシステムキャッシュ動作とより短いメッセージおよびプロセッサーキューの長さを提供し、通常、複数のコアがL3キャッシュを共有します。
CPUがデータを読み取るとき、CPUはまずL1キャッシュ、次にL2キャッシュ、次にL3キャッシュ、次にメモリ、そして外部ストレージハードディスクを調べます。
次の図に示すように、CPUキャッシュアーキテクチャでは、キャッシュレベルがCPUコアに近いほど、容量は小さく、速度は速くなります。CPUキャッシュは複数のキャッシュラインで構成されます。キャッシュラインはCPUキャッシュの最小単位です。キャッシュラインのサイズは通常64バイトで、2の倍数です。マシンごとに32バイトから64バイトまでさまざまで、効果的です。メインメモリのアドレスブロックを参照します。
複数のCPUが同じデータを読み取ってキャッシュした後、異なる計算を実行した後、最終的にどのCPUがメインメモリに書き込みますか?これには、キャッシュ同期プロトコルが必要です。
キャッシュ同期プロトコル
このキャッシュライトバックシナリオでは、多くのCPUメーカーがいくつかの一般的なプロトコルを提案しています。MESIプロトコルは、各キャッシュにステータスビットがあり、次の4つの状態を定義します。
- 変更:キャッシュラインが変更され(ダーティライン)、内容がメインメモリと異なるため、このキャッシュは排他的です。
- 排他:このキャッシュラインの内容はメインメモリと同じですが、他のキャッシュには表示されません。
- 共有(共有):このキャッシュラインの内容はメインメモリと同じですが、他のキャッシュにも表示されます。
- 無効な状態:このキャッシュラインの内容は無効です(空白行)。
マルチプロセッサ、単一のCPUがキャッシュ内のデータを変更する場合、他のCPUに通知する必要があります。つまり、CPU処理は自身の読み取りおよび書き込み操作を制御し、他のCPUから送信される通知を監視して、結果の一貫性を確保する必要があります。
実行時のコマンドの並べ替え
キャッシュに加えて、CPUのパフォーマンス最適化には、実行時の命令の再配置もあります。
たとえば、コードx = 10; y = z;の図では、このコードの通常の実行順序は、xに10を書き込み、zの値を読み取り、次にzの値をyに書き込むことです。実際、実際の実行ステップは、CPU実行時、zの値を最初に読み取り、zの値をyに書き込み、最後に10をxに書き込むことが必要な場合がありますが、なぜこれらの変更が行われるのですか?
CPUがキャッシュに書き込む際に、他のCPU(L3キャッシュなど)がキャッシュ領域を占有していることが判明したため、CPUの処理性能を向上させるために、次のリードキャッシュコマンドを先に実行する場合があります。
命令の並べ替えはランダムな並べ替えではありません。as-if-serialセマンティクスに準拠する必要があります。as-if-serialセマンティクスは、並べ替え(並列処理を改善するためのコンパイラとプロセッサ)、シングルスレッドプログラム実行に関係なく、結果は変更できません。コンパイラー、ランタイム、およびプロセッサーはすべて、as-if-serialセマンティクスに準拠する必要があります。つまり、コンパイラーとプロセッサーは、データに依存する操作を再配列しません。
次に、次の2つの問題があります。
- CPUキャッシュに問題があります:
キャッシュ内のデータとメインメモリ内のデータはリアルタイムで同期されず、CPU(またはCPUコア)間でキャッシュされたデータはリアルタイムで同期されません。同時に、各CPUから見た同じメモリアドレスのデータの値に一貫性がない場合があります。
- CPUの並べ替えの最適化に問題があります。
as-if-serialセマンティクスは順守されていますが、単一のCPUが単独で実行した場合にのみ、結果が正しいことが保証されます。マルチコアマルチスレッディングでは、命令ロジックが原因と結果の関係を区別できず、実行順序が乱れ、プログラムの実行結果が不正確になる可能性があります。
上記の2つの問題を解決する方法、これはメモリの障壁について話す必要があります。
メモリーバリア
プロセッサは、上記の2つの問題を解決するために2つのメモリバリア命令を提供します。
ストアメモリバリア:ストアバリアは、書き込みキャッシュ内の最新データをメインメモリに書き込み、他のスレッドから見えるようにするために、命令の後に挿入されます。この種のディスプレイコールであるメインメモリへの書き込みを強制すると、CPUはパフォーマンスを考慮して命令を並べ替えません。
ロードメモリバリア:命令の前にロードバリアを挿入すると、キャッシュ内のデータが無効になり、新しいメインメモリからデータを強制的にロードできます。メインメモリの内容を強制的に読み取り、CPUキャッシュとメインメモリの整合性を保ち、キャッシュによって引き起こされる整合性の問題を回避します。
Javaには、メモリバリアの原理を使用するSynchronizedやvolatileなどの同様のメカニズムがあります。
まとめ
この記事では主に、CPUがプログラムのパフォーマンスを改善するために行った最適化、つまり、キャッシュとランタイムの命令の再配置について紹介し、最後にメモリバリアの知識を紹介します。
リファレンスhttp://dwz.win/7ps