先週、スレッド間の通信を可能にするために、小さなスレッドクラスと一方向のメッセージパイプを作成しました(明らかに、双方向通信のために、スレッドごとに2つのパイプ)。 Athlon 64 X2ではすべて正常に機能しましたが、両方のスレッドが同じ変数を参照していて、各コアのこの変数のローカルキャッシュ値が同期していない場合、問題が発生するかどうか疑問に思いました。
volatileキーワードによって変数がメモリから強制的に更新されることはわかっていますが、マルチコアx86プロセッサで、すべてのコアのキャッシュを強制的に同期させる方法はありますか?これは私が心配する必要があるものですか、それともvolatileそして軽量ロックメカニズムの適切な使用(私は_InterlockedExchangeを使用して揮発性パイプ変数を設定していました)は「ロックフリー」を書きたいすべてのケースを処理しますマルチコアx86CPUのコード?
私はすでにクリティカルセクション、ミューテックス、イベントなどを認識して使用しています。キャッシュコヒーレンシを強制するためにどの力を使用できるかを知らないx86組み込み関数があるかどうか、ほとんど疑問に思っています。
volatile
は、コードに値の再読み取りを強制するだけで、値の読み取り元を制御することはできません。値が最近コードによって読み取られた場合は、おそらくキャッシュ内にあります。その場合、volatileは、メモリからではなく、キャッシュから値を強制的に再読み取りします。
X86には多くのキャッシュコヒーレンシ命令はありません。 prefetchnta
のようなプリフェッチ命令がありますが、それはメモリ順序のセマンティクスには影響しません。以前は、L2を汚染せずに値をL1キャッシュに取り込むことで実装されていましたが、大規模な共有を含むL3キャッシュを備えた最新のIntel設計では、事態はより複雑になります。 。
x86 CPUは、 MESIプロトコル (Intelの場合はMESIF、AMDの場合はMOESI)のバリエーションを使用して、キャッシュを相互にコヒーレントに保ちます(異なるコアのプライベートL1キャッシュを含む)。キャッシュラインを書き込みたいコアは、自身のコピーを共有状態から変更状態に変更する前に、他のコアにそのコピーを無効にするように強制する必要があります。
X86のロード/ストアには 取得/解放セマンティクス が組み込まれているため、1つのスレッドでデータを生成して別のスレッドでデータを消費するためのフェンス命令(MFENCEなど)は必要ありません。逐次一貫性を得るには、MFENCE(完全なバリア)が必要です。 (この回答の以前のバージョンでは、clflush
が必要であることが示唆されていましたが、これは正しくありません)。
C++のメモリモデルは順序が弱いため、 コンパイル時の並べ替え を防ぐ必要があります。 volatile
は、これを行うための古くて悪い方法です。 C++ 11 std :: atomicは、ロックフリーコードを作成するためのはるかに優れた方法です。
X86プロセッサで採用されているMESIプロトコルにより、コア間のキャッシュコヒーレンスが保証されます。データがコアのキャッシュにある間にメモリにアクセスする可能性のある外部ハードウェアを扱う場合にのみ、メモリの一貫性について心配する必要があります。ただし、テキストはユーザーランドでプログラミングしていることを示唆しているため、ここではあなたのケースのようには見えません。
キャッシュの一貫性について心配する必要はありません。ハードウェアがそれを処理します。心配する必要があるのは、そのキャッシュの一貫性によるパフォーマンスの問題です。
Core#1が変数に書き込むと、他のコアのキャッシュラインの他のすべてのコピーが無効になります(ストアをコミットする前に、キャッシュラインの 排他的所有権 を取得する必要があるため)。 core#2が同じ変数を読み取ると、キャッシュで失われます(core#1がキャッシュの共有レベルまで既に書き戻している場合を除く)。
キャッシュライン全体(64バイト)をメモリから読み取る(または共有キャッシュに書き戻してからcore#2で読み取る)必要があるため、パフォーマンスコストが発生します。この場合、それは避けられません。これは望ましい動作です。
問題は、同じキャッシュラインに複数の変数がある場合、コアが同じキャッシュライン内で異なる変数を読み書きしている場合でも、プロセッサがキャッシュの同期を維持するために余分な時間を費やす可能性があることです。
これらの変数が同じキャッシュラインにないことを確認することで、そのコストを回避できます。この効果は、実際にはスレッド間で共有されていないオブジェクトの値をプロセッサに同期させるため、偽共有として知られています。
揮発性はそれをしません。 C++では、volatileは、メモリではなくレジスタに変数を格納したり、変数を完全に削除したりするなど、コンパイラの最適化にのみ影響します。
あなたの質問にはいくつかのサブ質問があるので、私の知る限りそれらに答えます。
最新のメモリアーキテクチャを説明する一連の記事があります ここIntel Core2キャッシュ およびより多くの最新のアーキテクチャトピックを含みます。
記事は非常に読みやすく、よく説明されています。楽しい !
以下は、スレッド化されたプログラムを使用したvolatile
の使用に関する優れた記事です。
ハーブサッターは単純に 提案 任意の2つの変数が別々のキャッシュラインに存在する必要があるように見えました。彼は、ロックとノードポインターの間にパディングを使用して、並行キューでこれを実行します。
編集:IntelコンパイラまたはGCCを使用している場合は、 アトミックビルトイン を使用できます。これは、可能な場合はキャッシュをプリエンプションするために最善を尽くしているようです。