17

C++ Atomic Types and Operations に関する C ++0x の提案から:

29.1 順序と一貫性 [atomics.order]

次のパラグラフを含む新しいサブ条項を追加します。

列挙memory_orderは、[N2334 または採用された後継によって追加された新しいセクション] で定義されているように、詳細な通常の (非アトミック) メモリ同期順序を指定し、操作の順序付けを提供する場合があります。その列挙値とその意味は次のとおりです。

  • memory_order_relaxed

この操作はメモリを順序付けません。

  • memory_order_release

影響を受けるメモリ位置で解放操作を実行し、それが適用されるアトミック変数を通じて、通常のメモリ書き込みを他のスレッドから見えるようにします。

  • memory_order_acquire

影響を受けるメモリ位置で取得操作を実行し、それが適用されるアトミック変数を介して解放された他のスレッドでの通常のメモリ書き込みを、現在のスレッドから見えるようにします。

  • memory_order_acq_rel

この操作には、取得と解放の両方のセマンティクスがあります。

  • memory_order_seq_cst

この操作には、取得セマンティクスと解放セマンティクスの両方があり、さらに、操作の順番に一貫性があります。

提案の下:

bool A::compare_swap( C& expected, C desired,
        memory_order success, memory_order failure ) volatile

ここで、CAS のメモリ順序を指定できます。


私の理解では、「<code>memory_order_acq_rel」は操作に必要なメモリ ロケーションのみを同期し、他のメモリ ロケーションは同期されないままになる可能性があります (メモリ フェンスとして動作しません)。

さて、私の質問は、「<code>memory_order_acq_rel」を選択compare_swapして整数などの整数型に適用する場合、これは通常、マルチコア Intel i7 などの最新のコンシューマ プロセッサのマシン コードにどのように変換されるのでしょうか? 他の一般的に使用されているアーキテクチャ (x64、SPARC、ppc、arm) はどうですか?

具体的には (gcc などの具体的なコンパイラを想定):

  1. 上記の操作で整数の位置を比較して交換する方法は?
  2. そのようなコードはどのような命令シーケンスを生成しますか?
  3. i7での操作はロックフリーですか?
  4. このような操作は、完全なキャッシュ コヒーレンス プロトコルを実行し、i7 のメモリ フェンスであるかのように、異なるプロセッサ コアのキャッシュを同期しますか? それとも、この操作に必要なメモリ位置を同期するだけですか?
  5. 前の質問に関連して - acq_reli7 でセマンティクスを使用すると、パフォーマンス上の利点はありますか? 他のアーキテクチャはどうですか?

すべての答えをありがとう。

4

2 に答える 2

8

ここでの答えは簡単ではありません。正確に何が起こり、何を意味するかは、多くのことに依存しています。キャッシュコヒーレンス/メモリの基本的な理解のために、おそらく私の最近のブログエントリが役立つかもしれません:

しかし、それはさておき、いくつかの質問に答えてみましょう。まず、以下の関数は、サポートされているものに関して非常に期待されています。つまり、メモリ順序の保証がどれだけ強力になるかを非常にきめ細かく制御します。これは、コンパイル時の並べ替えには妥当ですが、実行時の障壁には適さないことがよくあります。

compare_swap( C& expected, C desired,
        memory_order success, memory_order failure )

アーキテクチャはすべて、要求どおりにこれを実装できるわけではありません。多くの人は、それを実装できるほど強力なものに強化する必要があります。memory_orderを指定すると、並べ替えがどのように機能するかを指定します。インテルの用語を使用するには、必要なフェンスのタイプを指定します。フルフェンス、ロードフェンス、ストアフェンスの3つがあります。(ただし、x86では、ロードフェンスとストアフェンスは、NTストアのように順序が弱い命令でのみ役立ちます。アトミックはそれらを使用しません。通常のロード/ストアでは、ストアが後でロードされた後に表示されることを除いて、すべてが提供されます。)その操作の特定のフェンスは、それがサポートされていることを意味するわけではありません。常に完全なフェンスにフォールバックすることを願っています。(メモリバリアに関するPreshingの記事を参照してください)

x86(x64を含む)コンパイラはLOCK CMPXCHG、メモリの順序に関係なく、CASを実装するために命令を使用する可能性があります。これは完全な障壁を意味します。x86には、プレフィックスなしで読み取り-変更-書き込み操作をアトミックにする方法がありませんlock。これも完全な障壁です。純粋なストアと純粋なロードは「それ自体」でアトミックになる可能性があり、多くのISAはそれ以上のものに対してバリアを必要としますmo_relaxedが、x86はacq_relasmで「無料」で実行します。

この命令はロックフリーですが、同じ場所をCASしようとするすべてのコアがアクセスを争うため、実際には待機フリーではないと主張することができます。(これを使用するアルゴリズムはロックフリーではない可能性がありますが、操作自体は待機フリーです。ウィキペディアのノンブロッキングアルゴリズムの記事を参照してください)。ed命令の代わりにLL/SCを使用する非x86では、 C ++ 11は通常待機なしですが、誤った障害が発生した場合に再試行ループが必要です。lockcompare_exchange_weakcompare_exchange_strong

C ++ 11が何年も存在しているので、Godboltコンパイラエクスプローラでさまざまなアーキテクチャのasm出力を確認できます。


メモリ同期に関しては、キャッシュコヒーレンスがどのように機能するかを理解する必要があります(私のブログは少し役立つかもしれません)。新しいCPUはccNUMAアーキテクチャ(以前はSMP)を使用します。基本的に、メモリの「ビュー」が同期しなくなることはありません。コードで使用されているフェンスは、実際にはキャッシュのフラッシュ自体を強制するものではなく、後でロードする前にフライトストアでキャッシュをコミットするストアバッファーのみを強制します。

2つのコアの両方が同じメモリ位置をキャッシュラインにキャッシュしている場合、1つのコアによるストアは、キャッシュラインの排他的所有権を取得し(他のすべてのコピーを無効にし)、自身をダーティとしてマークします。非常に複雑なプロセスの非常に簡単な説明

最後の質問に答えるには、論理的に正しい必要があるメモリセマンティクスを常に使用する必要があります。ほとんどのアーキテクチャは、プログラムで使用するすべての組み合わせをサポートするわけではありません。ただし、多くの場合、特に要求した順序がフェンスなしで保証されている場合(これは非常に一般的です)、優れた最適化が得られます。

-いくつかのコメントへの回答:

書き込み命令を実行することとメモリ位置に書き込むことの意味を区別する必要があります。これは私が私のブログ投稿で説明しようとしていることです。「0」が0x100にコミットされるまでに、すべてのコアはそのゼロを認識します。整数の書き込みもアトミックです。つまり、ロックがなくても、ある場所に書き込むと、すべてのコアがその値を使用したい場合はすぐにその値を持ちます。

問題は、最初にレジスタにロードした可能性のある値を使用するために、その後の場所への変更が明らかにレジスタに影響を与えないことです。これが、ミューテックスが必要な理由、またはatomic<T>キャッシュコヒーレントメモリにもかかわらずです。コンパイラは、プレーン変数値をプライベートレジスタに保持できます。(C ++ 11では、非atomic変数のデータ競合が未定義動作であるためです。)

矛盾する主張に関しては、一般的にあらゆる種類の主張が見られます。それらが矛盾しているかどうかは、文脈において「見る」「ロードする」「実行する」が正確に何を意味するかということになります。0x100に「1」を書き込んだ場合、それは書き込み命令を実行したことを意味しますか、それともCPUが実際にその値をコミットしたことを意味しますか。ストアバッファによって生じる違いは、並べ替えの主な原因の1つです(x86で許可されるのは1つだけです)。CPUは「1」の書き込みを遅らせることができますが、最終的に「1」をコミットした瞬間に、すべてのコアがそれを認識します。フェンスは、ストアがコミットするまでスレッドを待機させてから、後の操作を実行することにより、この順序を制御します。

于 2010-11-18T11:34:32.967 に答える
1

あなたの世界観全体はベースから外れているようです: あなたの質問は、キャッシュの一貫性が C++ レベルのメモリ順序とフェンスまたは CPU レベルのアトミック操作によって制御されていることをほのめかしています。

しかし、キャッシュの一貫性は、物理アーキテクチャにとって最も重要な不変条件の 1 つであり、すべての CPU と RAM の相互接続で構成されるメモリ システムによって常に提供されます。CPU で実行されているコードからは決して勝てず、操作の詳細を確認することさえできません。もちろん、RAM を直接観察して別の場所でコードを実行すると、あるレベルのメモリで古いデータが表示される場合があります。定義上、RAM はすべてのメモリ位置の最新の値を持っているわけではありません。

ただし、CPU 上で実行されるコードは DRAM に直接アクセスすることはできません。メモリ階層を介してのみアクセスできます。メモリ階層には、相互に通信してメモリの共有ビューの一貫性を維持するキャッシュが含まれます。(通常は MESI を使用)。単一のコアでも、ライトバック キャッシュによって DRAM 値が古くなります。これは、非キャッシュ コヒーレント DMA では問題になる可能性がありますが、CPU からのメモリの読み取り/書き込みでは問題になりません。

したがって、この問題は外部デバイスにのみ存在し、非コヒーレント DMA を実行するデバイスにのみ存在します。(DMA は最新の x86 CPU でキャッシュ コヒーレントです。CPU に組み込まれているメモリ コントローラーがこれを可能にします)。

このような操作は、完全なキャッシュ コヒーレンス プロトコルを実行し、i7 のメモリ フェンスであるかのように、異なるプロセッサ コアのキャッシュを同期しますか?

それらはすでに同期されています。メモリ バリアによって、キャッシュ コヒーレンスが完了したことが保証されますか?を参照してください。- メモリ バリアは、ストア バッファのフラッシュなど、バリアを実行しているコア内のローカル処理のみを行います。

それとも、この操作に必要なメモリ位置を同期するだけですか?

アトミック操作は、正確に 1 つのメモリ ロケーションに適用されます。他にどのような場所を考えていますか?

弱い順序付けの CPU では、memory_order_relaxedアトミック インクリメントにより、そのインクリメントの前に以前のロード/ストアが表示されることを回避できます。しかし、x86 の強く順序付けられたメモリ モデルでは、それができません。

于 2019-12-08T21:37:16.653 に答える