8.1.2バスロック
Intel 64およびIA-32プロセッサーは、特定の重要なメモリ操作中に自動的にアサートされてシステムバスまたは同等のリンクをロックするLOCK#信号を提供します。この出力信号がアサートされている間、他のプロセッサまたはバスエージェントからのバス制御の要求はブロックされます。ソフトウェアは、LOCKセマンティクスの後に命令の前にLOCKプレフィックスを付加する必要がある他の機会を指定できます。
これはIntel Manual、Volume 3からのものです
メモリのアトミック操作はメモリ(RAM)で直接実行されるようです。アセンブリの出力を分析すると、「特別なことは何もない」と表示されて混乱しています。基本的に、std::atomic<int> X; X.load()
に対して生成されたアセンブリ出力は、「余分な」mfenceのみを出力します。しかし、それは原子性ではなく、適切なメモリの順序付けに責任があります。私が正しく理解していれば、X.store(2)
はmov [somewhere], $2
。そして、それだけです。キャッシュを「スキップ」しないようです。アラインメント(intなど)をメモリに移動することはアトミックです。しかし、私は混乱しています。
だから、私は私の疑問を提示しましたが、主な質問は:
メモリのアトミック操作はメモリ(RAM)で直接実行されるようです。
いいえ、システム内のすべての可能なオブザーバーが操作をアトミックとして認識している限り、操作にはキャッシュのみが関係します。この要件を満たすことは、CPUがLOCK#信号をアサートする可能性がある場合、アトミックな読み取り/変更/書き込み操作(特に_lock add [mem], eax
_、特にアラインされていないアドレスの場合)では困難です。 asmにはそれ以上は表示されません。ハードウェアはlock
ed命令のISA必須のセマンティクスを実装します。
別の ノースブリッジチップ ではなく、メモリコントローラーがCPUに組み込まれている最近のCPUには、物理的な外部LOCK#ピンがあるとは思えません。
std::atomic<int> X; X.load()
は、「追加の」mfenceのみを配置します。
コンパイラーはseq_cstロードに対してMFENCEを行いません。 MSVCがこれに対してMFENCEを発行したことを読んだと思いますが(おそらくフェンスされていないNTストアでの再注文を防ぐためですか?)、それはもうありません:私はMSVC 19.00.23026.0をテストしました。 からのasm出力でfooとbarを探します。このプログラムは、独自のasmをオンラインのコンパイルと実行サイト にダンプします。
ここでフェンスが必要ない理由は、x86メモリモデルではLoadStoreとLoadLoadの両方の並べ替えが許可されていないためだと思います。以前の(seq_cst以外の)ストアは、seq_cstのロード後まで引き続き遅延する可能性があるため、std::atomic_thread_fence(mo_seq_cst);
の前にスタンドアロンのX.load(mo_acquire);
を使用する場合とは異なります。
私が正しく理解していれば、
X.store(2)
は_mov [somewhere], 2
_です
いいえ、seq_cstストア do には、完全にメモリバリアの命令が必要で、 StoreLoadの順序変更を禁止します 。
MSVCのストアのasmは clangの と同じで、xchg
を使用してストアとメモリバリアを同じ命令で実行します。 (一部のCPU、特にAMDでは、バリアとしてのlock
ed命令は、MFENCEよりも安価である可能性があります。IIRCAMDは、MFENCEの(命令の実行ではなく、命令の実行のための)追加のパイプラインセマンティクスをドキュメント化しているためです)。
この質問は、以前の C++のメモリモデルのパート2のように見えます:シーケンシャルな一貫性と原子性 、あなたが尋ねたところ:
CPUはどのように内部でアトミック操作を実装しますか?
質問で指摘したように、原子性は他の操作に関する順序とは無関係です。 (つまり、_memory_order_relaxed
_)。これは、操作が単一の分割不可能な操作として行われることを意味します。 したがって名前 は、何か他の前または後に部分的に発生する可能性がある複数の部分としてではありません。
アラインメントされたロードのための追加のハードウェアなしで「無料で」原子性を取得するか、コア、メモリ、およびPCIeなどのI/Oバス間のデータパスのサイズまで保存します。つまり、さまざまなレベルのキャッシュ間、および個別のコアのキャッシュ間。メモリコントローラーは最新の設計ではCPUの一部であるため、メモリにアクセスするPCIeデバイスでもCPUのシステムエージェントを経由する必要があります。 (これにより、SkylakeのeDRAM L4(デスクトップCPUでは使用できません:()が(L3 IIRCのビクティムキャッシュとして使用したBroadwellとは異なり)メモリ側キャッシュとして機能し、システム内のメモリと他のすべての間に置かれます。 DMAをキャッシュすることもできます)。
つまり、CPUハードウェアは、 anything に関してストアまたはロードがアトミックであることを確認するために、それを監視できるシステム内でアトミックであることを確認するために必要なすべてを実行できます。おそらく、これはおそらくそれほど多くありません。 DDRメモリは、64ビットアラインメントストアが実際に電気的にメモリバスを介してすべて同じサイクルでDRAMに到達するのに十分な広さのデータバスを使用します。 (楽しい事実ですが、重要ではありません。PCIeのようなシリアルバスプロトコルは、単一のメッセージが十分に大きい限り、アトミックであることを妨げるものではありません。DRAMと直接通信できるのはメモリコントローラーだけなので、内部で何をするかは関係なく、CPUと残りのCPUの間の転送のサイズだけです)。しかし、とにかく、これは「無料」の部分です。アトミック転送をアトミックに保つために他のリクエストを一時的にブロックする必要はありません。
x86は、整列されたロードと64ビットまでのストアがアトミックであることを保証します 、しかしより広いアクセスではありません。低電力の実装では、PIIIからPentium MまでのP6のように、ベクトルのロード/ストアを64ビットのチャンクに自由に分割できます。
アトミックとは、すべての観測者がそれを発生した、または発生していない、部分的に発生したと見なさないことを意味することを忘れないでください。実際にメインメモリにすぐに到達する必要はありません(または、すぐに上書きされる場合はまったく)。 L1キャッシュを原子的に変更または読み取るだけで、他のコアまたはDMAアクセスで、整列されたストアまたはロードが単一のアトミック操作として発生することを確認できます。ストアの実行後、この変更が長時間発生しても問題ありません(たとえば、ストアが終了するまで、順不同の実行により遅延します)。
あらゆる場所に128ビットパスを持つCore2のような最近のCPUは、通常、アトミックSSE 128bロード/ストアを持ち、x86 ISAが保証するものを超えています。しかし、興味深い例外に注意してください。 おそらくハイパートランスポートが原因であるマルチソケットOpteronの場合 これは、L1キャッシュを原子的に変更するだけでは、最も狭いデータパス(この場合は、 L1キャッシュと実行ユニット)。
アライメントは重要です:キャッシュラインの境界を越えるロードまたはストアは、2つの個別のアクセスで実行する必要があります。これにより、非アトミックになります。
x86は、AMD/Intelで8B境界を超えない限り、最大8バイトのキャッシュされたアクセスがアトミックであることを保証します 。 (またはIntelのP6以降でのみ、キャッシュラインの境界を越えないでください)。これは、データパス(Haswell/SkylakeのL2とL3の間の32B)より広いにもかかわらず、キャッシュライン全体(最近のCPUでは64B)がアトミックに転送されることを意味します。この原子性はハードウェアで完全に「フリー」ではなく、部分的にのみ転送されるキャッシュラインをロードが読み取らないようにするために、追加のロジックが必要になる場合があります。キャッシュライン転送は古いバージョンが無効化された後にのみ発生するため、転送が発生している間はコアが古いコピーから読み取ってはなりません。 AMDは実際には小さな境界で破損する可能性があります。これは、キャッシュ間でダーティデータを転送できるMESIの別の拡張機能を使用しているためと考えられます。
構造体の複数のエントリにアトミックに新しいデータを書き込むなどのより広いオペランドの場合、すべてのアクセスが尊重するロックでそれを保護する必要があります。 (再試行ループでx86 _lock cmpxchg16b
_を使用して、アトミック16bストアを実行できる場合があります。 ミューテックスなしでエミュレートする方法はありません 。)
Atomic read-modify-writeは難しくなる場所です
関連: での私の答えnum ++は 'int num'に対してアトミックですか? これについて詳しく説明します。
各コアには、他のすべてのコアとコヒーレントなプライベートL1キャッシュがあります( [〜#〜] moesi [〜#〜] プロトコルを使用)。キャッシュラインは、64ビットから256ビットの範囲のサイズのチャンクで、キャッシュレベルとメインメモリの間で転送されます。 (これらの転送は実際にはキャッシュライン全体の粒度でアトミックである可能性がありますか?)
アトミックRMWを実行するために、コアは、ロードとストアの間の影響を受けるキャッシュラインへの外部変更を受け入れることなく、L1キャッシュのラインを変更状態に保つことができます。システムの残りの部分は、操作をアトミックと見なします。 (したがって、 is atomicです。通常のアウトオブオーダー実行ルールでは、ローカルスレッドが独自のコードをプログラムの順序で実行されていると見なす必要があるためです。)
これは、アトミックRMWが処理中の間は、キャッシュコヒーレンシメッセージを処理しないことでこれを実行できます(または、他の操作に対してより多くの並列処理を可能にする、これのいくつかのより複雑なバージョン)。
整列されていないlock
ed opsは問題です。2つのキャッシュラインへの変更が1つのアトミック操作として発生することを確認するには、他のコアが必要です。 This は、実際にDRAMに保存し、バスロックを取得する必要がある場合があります。 (AMDの最適化マニュアルでは、これは、キャッシュロックが不十分な場合にCPUで発生することです。)
LOCK#信号(cpuパッケージ/ソケットのピン)が(LOCK
接頭辞付きアトミック操作の)古いチップで使用されていましたが、キャッシュロックがあります。そして、_.exchange
_や_.fetch_add
_などのより複雑なアトミック操作の場合は、 LOCK
prefix 、または他の種類のアトミック命令(cmpxchg/8 /)で操作します16?).
同じマニュアル、システムプログラミングガイドの部分:
Pentium 4、Intel Xeon、およびP6ファミリプロセッサでは、ロック操作はキャッシュロックまたはバスロックで処理されます。メモリアクセスがキャッシュ可能で、1つのキャッシュラインのみに影響する場合、キャッシュロックが呼び出され、システムバスおよびシステムメモリ内の実際のメモリ位置は操作中にロックされません。
Paul E. McKenneyの論文や本を確認できます。* 最新のマイクロプロセッサでのメモリの順序付け 、2007 * メモリの障壁:ソフトウェアハッカーのハードウェアビュー 、2010 * perfbook 、 " 並列プログラミングは難しいですか、そうであれば、それについて何ができますか? "
そして、* Intel 64 Architecture Memory Ordering White Paper 、2007。
ロードの順序変更を防ぐために、x86/x86_64にはメモリバリアが必要です。最初の論文から:
x86(..AMD64はx86と互換性があります。)x86 CPUは「プロセスの順序付け」を提供するため、すべてのCPUが特定の順序に同意します。 CPUのメモリへの書き込みである
smp_wmb()
プリミティブは、CPUには何もしません[7]。ただし、コンパイラーがsmp_wmb()
プリミティブ全体で再順序付けを引き起こす最適化を実行しないようにするには、コンパイラー指示が必要です。一方、x86 CPUは従来、ロードの順序付けを保証していなかったため、
smp_mb()
およびsmp_rmb()
プリミティブは_lock;addl
_に展開されます。このアトミック命令は、ロードとストアの両方に対するバリアとして機能します。
(2枚目の論文から)メモリバリアを読み取るもの:
この効果は、読み取りメモリバリアはそれを実行するCPUへのロードのみを順序付けするため、読み取りメモリバリアに先行するすべてのロードは、読み取りメモリバリアに続くロードの前に完了したように見えます。
たとえば、「Intel 64 Architecture Memory Ordering White Paper」から
Intel 64メモリの順序付けにより、以下の各メモリアクセス命令について、メモリの種類に関係なく、構成要素のメモリ操作が単一のメモリアクセスとして実行されるように見えます。..アドレスがダブルワード(4バイト)の読み取りまたは書き込みを行う命令4バイト境界で整列されます。
Intel 64メモリの順序付けは、次の原則に従います。1.ロードは他のロードと並べ替えられません。 ... 5.マルチプロセッサシステムでは、メモリの順序は因果律に従います(メモリの順序は推移的な可視性を尊重します)。 ... Intel 64メモリの順序付けにより、プログラムの順序でロードが確認されます
また、mfence
の定義: http://www.felixcloutier.com/x86/MFENCE.html
MFENCE命令の前に発行されたすべてのload-from-memoryおよびstore-to-memory命令でシリアル化操作を実行します。このシリアル化操作により、プログラムの順序でMFENCE命令の前にあるすべてのロードおよびストア命令が、MFENCE命令に続くロードまたはストア命令の前にグローバルに表示されることが保証されます。