19

私はFull fences、そのフェンスの周りで(memoryBarrierを介して)あらゆる種類の命令の並べ替えやキャッシュを防ぐことを読んでいます

volatile 次に、どちらが「ハーフフェンス」を生成するかについて読みました。

volatileキーワードは、そのフィールドからのすべての読み取りで取得フェンスを生成し、そのフィールドへのすべての書き込みでリリースフェンスを生成するようにコンパイラーに指示します。

acquire-fence

取得フェンスは、他の読み取り/書き込みがフェンスの前に移動するのを防ぎます。

release-fence

リリースフェンスは、他の読み取り/書き込みがフェンスの後に移動するのを防ぎます。

誰かが私にこれらの2つの文を簡単な英語で説明してもらえますか?

(柵はどこにありますか?)

編集

ここでいくつかの答えをした後-私は皆を助けることができる絵を作りました-私は思います。

https://i.stack.imgur.com/A5F7P.jpg ここに画像の説明を入力してください

4

3 に答える 3

20

あなたが言及している言葉遣いは、私がよく使う言葉のように見えます。ただし、仕様には次のように記載されています。

  • 揮発性フィールドの読み取りは、揮発性読み取りと呼ばれます。揮発性読み取りには「取得セマンティクス」があります。つまり、命令シーケンスでメモリへの参照の後に発生するメモリへの参照の前に発生することが保証されています。
  • 揮発性フィールドの書き込みは、揮発性書き込みと呼ばれます。揮発性書き込みには「リリースセマンティクス」があります。つまり、命令シーケンスの書き込み命令の前のメモリ参照の後に発生することが保証されています。

ただし、指示を移動できることに焦点を当てたいので、私は通常、質問で引用した表現を使用します。あなたが引用した言葉遣いと仕様は同等です。

いくつかの例を紹介します。これらの例では、リリースフェンスを示すために↑矢印を使用し、取得フェンスを示すために↓矢印を使用する特別な表記法を使用します。他の命令は、↑矢印を超えて下にフロートしたり、↓矢印を超えて上にフロートしたりすることはできません。鏃はそれからすべてをはじくものと考えてください。

次のコードを検討してください。

static int x = 0;
static int y = 0;

static void Main()
{
  x++
  y++;
}

個々の指示を表示するように書き直すと、次のようになります。

static void Main()
{
  read x into register1
  increment register1
  write register1 into x
  read y into register1
  increment register1
  write register1 into y
}

この例にはメモリバリアがないため、実行中のスレッドによって認識される論理シーケンスが物理シーケンスと一致している限り、C#コンパイラ、JITコンパイラ、またはハードウェアはさまざまな方法で自由に最適化できます。これがそのような最適化の1つです。読み取りと書き込みがどのように行われ、スワップされたかに注意してxくださいy

static void Main()
{
  read y into register1
  read x into register2
  increment register1
  increment register2
  write register1 into y
  write register2 into x
}

今回は、これらの変数をに変更しますvolatile。矢印表記を使用して、メモリバリアをマークします。読み取りと書き込みの順序がどのように保持されているかに注意してxくださいy。これは、指示がバリア(↓および↑の矢印で示されている)を超えて移動できないためです。さて、これは重要です。命令のインクリメントと書き込みxは引き続きフロートダウンし、読み取りはyフロートアップできることに注意してください。ハーフフェンスを使用していたため、これは引き続き有効です。

static volatile int x = 0;
static volatile int y = 0;

static void Main()
{
  read x into register1
  ↓    // volatile read
  read y into register2
  ↓    // volatile read
  increment register1
  increment register2
  ↑    // volatile write
  write register1 into x
  ↑    // volatile write
  write register2 into y
}

これは非常に簡単な例です。ダブルチェックされたパターンにどのように違いをもたらすことができるかについての重要な例については、ここで私の答えを見てください。volatileここで使用したのと同じ矢印表記を使用して、何が起こっているかを簡単に視覚化できるようにします。

これで、操作する方法もありますThread.MemoryBarrier。フルフェンスを生成します。したがって、矢印表記を使用すると、それがどのように機能するかを視覚化できます。

この例を考えてみましょう。

static int x = 0;
static int y = 0;

static void Main
{
  x++;
  Thread.MemoryBarrier();
  y++;
}

以前のように個々の指示を表示する場合は、次のようになります。現在、命令の移動が完全に防止されていることに注意してください。命令の論理シーケンスを損なうことなくこれを実行できる方法は他にありません。

static void Main()
{
  read x into register1
  increment register1
  write register1 into x
  ↑    // Thread.MemoryBarrier
  ↓    // Thread.MemoryBarrier
  read y into register1
  increment register1
  write register1 into y
}

さて、もう1つの例。今回はVB.NETを使用しましょう。volatileVB.NETにはキーワードがありません。では、VB.NETで揮発性の読み取りをどのように模倣できますか?を使用しますThread.MemoryBarrier1

Public Function VolatileRead(ByRef address as Integer) as Integer
  Dim local = address
  Thread.MemoryBarrier()
  Return local
End Function

そして、これは私たちの矢印表記でどのように見えるかです。

Public Function VolatileRead(ByRef address as Integer) as Integer
  read address into register1
  ↑    // Thread.MemoryBarrier
  ↓    // Thread.MemoryBarrier
  return register1
End Function

揮発性の読み取りを模倣したいので、実際の読み取りのThread.MemoryBarrierに呼び出しを行う必要があることに注意することが重要です。揮発性の読み取りは「新規読み取り」を意味し、揮発性の書き込みは「コミットされた書き込み」を意味するという考えに陥らないでください。それはそれがどのように機能するかではなく、それは確かに仕様が説明しているものではありません。

アップデート:

画像を参考に。

待つ!すべての書き込みが終了したことを確認しています。

待つ!私はすべての消費者が現在の価値を持っていることを確認しています!

これは私が話していた罠です。ステートメントは完全に正確ではありません。はい、ハードウェアレベルで実装されたメモリバリアは、キャッシュコヒーレンシラインを同期する可能性があり、その結果、上記のステートメントは、何が起こっているかをある程度正確に把握している可能性があります。しかし、volatile指示の移動を制限するだけです。仕様では、メモリから値をロードしたり、メモリバリアが配置されている場所でメモリに値を保存したりすることについては何も述べていません。


1もちろん、Thread.VolatileReadすでにビルトインがあります。そして、あなたはそれが私がここで行ったのとまったく同じように実装されていることに気付くでしょう。

于 2012-05-14T20:57:41.273 に答える
3

他の方法から始めます:

揮発性フィールドを読み取るときに重要なことは何ですか?そのフィールドへの以前のすべての書き込みがコミットされたこと。

揮発性フィールドに書き込むときに重要なことは何ですか?以前のすべての読み取りがすでに値を取得していること。

次に、取得フェンスと解放フェンスがそのような状況で意味があることを確認してみてください。

于 2012-05-14T19:19:50.157 に答える
1

これについてもっと簡単に推論するために、任意の並べ替えが可能なメモリモデルを想定しましょう。

簡単な例を見てみましょう。この揮発性フィールドを想定します。

volatile int i = 0;

そして、この読み取り/書き込みのシーケンス:

1. int a = i;
2. i = 3;

の読み取りである命令1に対してi、取得フェンスが生成されます。つまり、書き込み先である命令2は、命令1と並べ替えることができないため、シーケンスの最後で3になるi可能性はありません。a

もちろん、単一のスレッドを検討する場合、上記はあまり意味がありませんが、別のスレッドが同じ値で動作する場合(aグローバルであると想定):

thread 1               thread 2
a = i;                 b = a;
i = 3;

この場合、スレッド2が3の値を取得する可能性はないと考えるでしょう(割り当てbの前または後の値を取得するため)。ただし、の読み取りと書き込みが並べ替えられると、値3を取得する可能性があります。この場合、プログラムの正当性が3にならないことに依存する場合は、揮発性にする必要があります。aa = i;ibib

免責事項:上記の例は理論上の目的のみです。コンパイラが完全に狂っていない限り、変数の「間違った」値を作成する可能性のある並べ替えを実行しません(つまり、揮発性でなくaても3にすることはできません)。i

于 2012-05-14T19:21:42.473 に答える