C11 Atomic Acquire / Releaseおよびx86_64ロード/ストアの一貫性の欠如?


10

C11標準のセクション5.1.2.4、特にリリース/取得のセマンティクスに苦労しています。私は、https://preshing.com/20120913/acquire-and-release-semantics/(他のものの中でも)が次のように述べていることに注意します

...リリースセマンティクスは、プログラムの順序でそれに先行する読み取りまたは書き込み操作による書き込みリリースのメモリの並べ替えを防ぎます。

したがって、次の場合:

typedef struct test_struct
{
  _Atomic(bool) ready ;
  int  v1 ;
  int  v2 ;
} test_struct_t ;

extern void
test_init(test_struct_t* ts, int v1, int v2)
{
  ts->v1 = v1 ;
  ts->v2 = v2 ;
  atomic_store_explicit(&ts->ready, false, memory_order_release) ;
}

extern int
test_thread_1(test_struct_t* ts, int v2)
{
  int v1 ;
  while (atomic_load_explicit(&ts->ready, memory_order_acquire)) ;
  ts->v2 = v2 ;       // expect read to happen before store/release 
  v1     = ts->v1 ;   // expect write to happen before store/release 
  atomic_store_explicit(&ts->ready, true, memory_order_release) ;
  return v1 ;
}

extern int
test_thread_2(test_struct_t* ts, int v1)
{
  int v2 ;
  while (!atomic_load_explicit(&ts->ready, memory_order_acquire)) ;
  ts->v1 = v1 ;
  v2     = ts->v2 ;   // expect write to happen after store/release in thread "1"
  atomic_store_explicit(&ts->ready, false, memory_order_release) ;
  return v2 ;
}

それらが実行される場所:

>   in the "main" thread:  test_struct_t ts ;
>                          test_init(&ts, 1, 2) ;
>                          start thread "2" which does: r2 = test_thread_2(&ts, 3) ;
>                          start thread "1" which does: r1 = test_thread_1(&ts, 4) ;

したがって、スレッド "1"にはr1 == 1、スレッド "2"にはr2 = 4が必要です。

(セクション5.1.2.4の16項と18項に従って)次の理由により、

  • すべての(アトミックではない)読み取りと書き込みは「前にシーケンス」され、したがってスレッド「1」でのアトミックな書き込み/解放の「前に発生」します。
  • どの「inter-thread-happens-before」は、スレッド「2」でアトミックな読み取り/取得(「true」を読み取った場合)、
  • これは順番に「前にシーケンス」され、したがって「アトミックではなく」前に発生します(スレッド「2」で)読み取りと書き込みを行います。

ただし、基準を理解できなかった可能性は十分にあります。

x86_64用に生成されたコードには次のものが含まれています。

test_thread_1:
  movzbl (%rdi),%eax      -- atomic_load_explicit(&ts->ready, memory_order_acquire)
  test   $0x1,%al
  jne    <test_thread_1>  -- while is true
  mov    %esi,0x8(%rdi)   -- (W1) ts->v2 = v2
  mov    0x4(%rdi),%eax   -- (R1) v1     = ts->v1
  movb   $0x1,(%rdi)      -- (X1) atomic_store_explicit(&ts->ready, true, memory_order_release)
  retq   

test_thread_2:
  movzbl (%rdi),%eax      -- atomic_load_explicit(&ts->ready, memory_order_acquire)
  test   $0x1,%al
  je     <test_thread_2>  -- while is false
  mov    %esi,0x4(%rdi)   -- (W2) ts->v1 = v1
  mov    0x8(%rdi),%eax   -- (R2) v2     = ts->v2   
  movb   $0x0,(%rdi)      -- (X2) atomic_store_explicit(&ts->ready, false, memory_order_release)
  retq   

そして、提供 R1とX1は、その順序で起こることを、これは私が期待する結果を与えます。

しかし、x86_64についての私の理解では、読み取りは他の読み取りと順番に行われ、他の書き込みと順番に行われますが、読み取りと書き込みは順番に行われない場合があります。つまり、X1がR1の前に発生する可能性があり、X1、X2、W2、R1がこの順序で発生する可能性もあります。[これは絶対にありそうにないように思われますが、R1がいくつかのキャッシュの問題によって停滞している場合は?]

どうぞ:私は何を理解していませんか?

のロード/ストアをに変更すると、ストアに対して生成されるコードは次のts->readyようmemory_order_seq_cstになります。

  xchg   %cl,(%rdi)

これはx86_64についての私の理解と一致しており、期待した結果が得られます。


5
x86では、すべての通常の(非一時的ではない)ストアにリリースセマンティクスがあります。インテル®64およびIA-32アーキテクチャー・ソフトウェア・デベロッパーズ・マニュアル第3巻(3A、3B、3C&3D):プログラミングガイドシステム8.2.3.3 Stores Are Not Reordered With Earlier Loads。そのため、コンパイラーはコードを正しく変換しています(驚くべきことです)。これにより、コードは実質的に完全にシーケンシャルになり、興味深いことは同時に発生しません。
EOF

ありがとうございました !(私は静かに行き詰まっていました。)FWIW私はリンクをお勧めします-特にセクション3、「プログラマーモデル」。しかし、私が陥った間違いを避けるために、「3.1 The Abstract Machine」では、「ハードウェアスレッド」があり、それぞれ「命令実行の単一の順序ストリーム」であることに注意してください(私の強調は追加されました)。C11標準を理解しようとすることに戻ることができます...認知的不協和音が少なくなります:-)
クリスホール

回答:


1

x86のメモリモデルは基本的に、シーケンシャルな一貫性に加えて、ストアバッファ(ストアフォワーディングを使用)です。したがって、すべてのストアはリリースストアです1。これが、seq-cstストアのみが特別な指示を必要とする理由です。(asmへのC / C ++ 11アトミックマッピング)。また、https: //stackoverflow.com/tags/x86/infoには、x86-TSOメモリモデルの正式な説明など x86のドキュメントへのリンクがいくつかあります(ほとんどの人にとっては基本的に判読できません。多くの定義に目を通す必要があります)。

:あなたはすでに記事のジェフPreshingの優れたシリーズを読んでいるので、私はより詳細になり、別の1であなたを指します https://preshing.com/20120930/weak-vs-strong-memory-models/

x86で許可されている唯一の並べ替えは、それらの用語で言えば、LoadStoreではなくStoreLoadです。(ロードがストアと部分的にしかオーバーラップしない場合、ストア転送は余分な面白さをもたらす可能性があります。グローバルに非表示のロード命令。ただし、のコンパイラー生成コードではこれを取得できませんstdatomic。)

@EOFはIntelのマニュアルからの正しい引用でコメントしました:

インテル®64およびIA-32アーキテクチャーソフトウェア開発者マニュアルボリューム3(3A、3B、3Cおよび3D):システムプログラミングガイド、8.2.3.3以前のロードではストアの順序が変更されない。


脚注1:弱く順序付けられたNTストアを無視します。これが、通常sfenceNTストアを実行した後の理由です。C11 / C ++ 11実装は、NTストアを使用していないことを前提としています。その場合は、_mm_sfenceリリース操作の前に使用して、NTストアを尊重するようにしてください。(通常、/ 使用しないで_mm_mfence_mm_sfenceください。通常は、コンパイル時の並べ替えをブロックするだけで済みます。もちろん、stdatomicを使用するだけです。)


私が見つけA厳格およびx86マルチプロセッサのために使用可能なプログラマモデル:のx86-TSOを(関連する)よりも読みやすい形式記述あなたが参照しました。しかし、私の真の野心は、C11 / C18標準のセクション5.1.2.4および7.17.3を完全に理解することです。特に、Release / Acquire / Acquire + Releaseを取得すると思いますが、memory_order_seq_cstが個別に定義されており、それらがどのように組み合わさるかを確認するのに苦労しています:-(
Chris Hall

@ChrisHall:acq / relがどれほど弱いかを正確に理解するのに役立つことがわかりました。そのためには、IRIWの並べ替えを実行できるPOWERなどのマシンを調べる必要があります。(これはseq-cstは禁止しますが、acq / relは禁止しません)。 異なるスレッドの異なる場所への2つのアトミックな書き込みは、他のスレッドによって常に同じ順序で表示されますか?。また、C ++ 11でStoreLoadバリアを実現する方法は?は、標準がsynchrize-withまたはEverything-seq-cstの外での順序付けについて正式に保証するものはほとんどないことについていくつかの議論があります。
Peter Cordes

@ChrisHall:seq-cstが行う主なことは、StoreLoadの並べ替えをブロックすることです。(x86では、acq / relを超えてそれが唯一のことです)。 preshing.com/20120515/memory-reordering-caught-in-the-actはasmを使用しますが、seq-cstとacq / relは同等です
Peter Cordes
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.