MemoryBarrier の使用法については明確ですが、ランタイムの舞台裏で何が起こっているかについては明確ではありません。誰が何が起こっているのかについて良い説明をすることができますか?
2 に答える
非常に強力なメモリ モデルでは、フェンス命令を発行する必要はありません。すべてのメモリ アクセスは順番に実行され、すべてのストアがグローバルに表示されます。
現在の一般的なアーキテクチャは強力なメモリ モデルを提供しないため、メモリ フェンスが必要です。たとえば、x86/x64 では、書き込みに対して読み取りを並べ替えることができます。(より完全な情報源は、「Intel® 64 and IA-32 Architectures Software Developer's Manual , 8.2.2 Memory Ordering in P6 and More Recent Processor Families」です)。膨大な数の例として、 Dekker のアルゴリズムはフェンスのないx86/x64 では失敗します。
メモリのロードとストアを伴う命令が慎重に配置されたマシン コードを JIT が生成したとしても、CPU がこれらのロードとストアの順序を変更すると、JIT の努力は役に立たなくなります。コンテキスト/スレッド。
単純化しすぎるリスク: 命令ストリームから生じるロードとストアを野生動物の雷鳴の群れとして視覚化すると役立つ場合があります。彼らが狭い橋 (あなたの CPU) を渡るとき、動物の順序について確信を持つことは決してできません。なぜなら、動物の一部はより遅く、一部はより速く、一部は追い越し、一部は遅れるからです。最初に (機械語コードを発行するときに)、それらの間に無限に長いフェンスを配置してそれらをグループに分割すると、少なくともグループ A がグループ B の前に来ることを確認できます。
フェンスは、読み取りと書き込みの順序を保証します。言葉遣いは正確ではありませんが、次のとおりです。
- ストア フェンスは、すべての未処理のストア (書き込み) 操作が完了するのを「待機」しますが、ロードには影響しません。
- ロード フェンスは、すべての未処理のロード (読み取り) 操作が完了するまで「待機」しますが、ストアには影響しません。
- 完全なフェンスは、すべてのストアおよびロード操作が完了するのを「待機」します。フェンスの前の読み取りと書き込みが、「フェンスの反対側」にある (フェンスより後に来る) 書き込みとロードの前に実行されるという効果があります。
完全なフェンスに対して JIT が発行するものは、(CPU) アーキテクチャと、それが提供するメモリの順序付けによって異なります。JIT は実行するアーキテクチャを正確に認識しているため、適切な命令を発行できます。
.NET 4.0 RC を使用する私の x64 マシンでは、たまたまlock or
.
int a = 0;
00000000 sub rsp,28h
Thread.MemoryBarrier();
00000004 lock or dword ptr [rsp],0
Console.WriteLine(a);
00000009 mov ecx,1
0000000e call FFFFFFFFEFB45AB0
00000013 nop
00000014 add rsp,28h
00000018 ret
Intel® 64 and IA-32 Architectures Software Developer's Manual Chapter 8.1.2:
「...ロックされた操作は、すべての未処理のロードおよびストア操作をシリアル化します (つまり、それらが完了するのを待ちます)。」 ... 「ロックされた操作は、他のすべてのメモリ操作および外部から見えるすべてのイベントに関してアトミックです。ロックされた命令を渡すことができるのは、命令フェッチとページテーブルアクセスのみです。ロックされた命令は、あるプロセッサによって書き込まれたデータと別のプロセッサによって読み取られたデータを同期するために使用できます。 ."
メモリ順序付け命令は、この特定のニーズに対応します。
MFENCE
上記の場合、完全なバリアとして使用できた可能性があります (少なくとも理論的には、ロックされた操作の方が高速である可能性があり、2 つは異なる動作になる可能性があります)。MFENCE
およびその仲間は、第8.2.5章「メモリ順序付けモデルの強化または弱体化」にあります。
ストアとロードをシリアル化する方法は他にもいくつかありますが、上記の方法よりも実用的ではないか、処理が遅くなります。
章 8.3 では、のような完全なシリアライズ手順を見つけることができます
CPUID
。これらのシリアライズ命令フローも同様です。メモリをストロング アンキャッシュ (UC) として設定すると、強力なメモリ モデルが得られます。投機的または順不同のアクセスは許可されず、すべてのアクセスがバス上に表示されるため、命令を発行する必要はありません。:) もちろん、これは通常より少し遅くなります。
...
だから依存する。強力な順序保証を備えたコンピューターがあれば、JIT はおそらく何も出力しないでしょう。
IA64 およびその他のアーキテクチャには、独自のメモリ モデルがあるため、メモリの順序付け (またはメモリの順序の欠如) が保証され、メモリのストア/ロードの順序付けを処理する独自の命令/方法があります。
ロックフリーの並行プログラミングを行っている間は、プログラム命令の並べ替えに注意する必要があります。
プログラム命令の並べ替えは、いくつかの段階で発生する可能性があります。
- C#/VB.NET/F# コンパイラの最適化
- JIT コンパイラーの最適化
- CPU の最適化。
メモリ フェンスは、プログラム命令の特定の順序を保証する唯一の方法です。基本的に、メモリフェンスは、CPU に順序制約を強制させる命令のクラスです。メモリ フェンスは、次の 3 つのカテゴリに分類できます。
- ロード フェンス - ロード CPU 命令がフェンスを越えて移動しないようにする
- ストア フェンス - ストア CPU 命令がフェンスを越えて移動しないようにする
- フル フェンス - ロードまたはストア CPU 命令がフェンスを越えて移動しないようにする
.NET Framework には、Interlock、Monitor、ReaderWriterLockSlim など、フェンスを発行する方法がたくさんあります。
Thread.MemoryBarrier は、JIT コンパイラとプロセッサ レベルの両方で完全なフェンスを発行します。