いつ_mm_sfence _mm_lfenceと_mm_mfenceを使用する必要があります
-
13-10-2019 - |
質問
「Intel Optimization Guide for Intel Architecture」を読みました。
しかし、私はまだいつ使用すべきかについてまだ知りません
_mm_sfence()
_mm_lfence()
_mm_mfence()
マルチスレッドコードを書くときにこれらを使用する必要があることを誰かが説明できますか?
解決
警告: :私はこれの専門家ではありません。私はまだこれを自分で学ぼうとしています。しかし、過去2日間は誰も返信していないため、メモリフェンスの指示の専門家は豊富ではないようです。だからここに私の理解があります...
Intelはaです 弱い順序 メモリシステム。つまり、プログラムが実行される可能性があります
array[idx+1] = something
idx++
しかし、への変更 idx に変更する前に、グローバルに表示される可能性があります(他のプロセッサで実行されているスレッド/プロセスなど) 配列. 。配置 sfence 2つのステートメントの間で、書き込みがFSBに送信される順序が保証されます。
一方、別のプロセッサが実行されます
newestthing = array[idx]
メモリをキャッシュした可能性があります 配列 古いコピーがありますが、更新されます idx キャッシュミスのため。解決策は使用することです lfence 荷重が同期されていることを確認するために、すぐに。
他のヒント
ここに私の理解があります。
(Itanium)IA64アーキテクチャにより、メモリの読み取り値を任意の順序で実行できるため、他のプロセッサの観点からのメモリの順序の変更は、妥当な順序で完全な書き込みを実施するためにフェンスを使用しない限り、予測できません。
これから、私はx86について話している、x86は強く注文されています。
X86では、Intelは、別のプロセッサで行われたストアがこのプロセッサで常にすぐに表示されることを保証しません。このプロセッサは、他のプロセッサのストア(書き込み)を見逃すのに十分な早期に負荷(読み取り)を投機的に実行した可能性があります。これは、他のプロセッサに表示されるようになる順序がプログラムの順序であることのみを保証します。他のプロセッサがあなたが何をしても、すぐに更新が表示されることを保証するものではありません。
ロックされた読み取り/変更/書き込み手順は完全に一貫しています。このため、一般的に、ロックされているため、他のプロセッサのメモリ操作が欠落していることをすでに処理します xchg
また cmpxchg
すべてを同期し、所有権のために関連するキャッシュラインをすぐに取得し、原子的に更新します。別のCPUがロックされたオペレーションでレースをしている場合、レースに勝ち、他のCPUはロックされた操作後にキャッシュを逃して元に戻すか、レースに勝ち、キャッシュを逃して更新を取得しますそれらからの価値。
lfence
命令の前のすべての指示まで命令を発行します lfence
完了しています。 mfence
具体的には、前のすべてのメモリ読み取りが宛先レジスタに完全に持ち込まれ、先行するすべての書き込みがグローバルに見えるようになるのを待ちますが、それ以上の指示はすべて停止しません。 lfence
します。 sfence
店のみで同じことをし、フラッシュはコンバイナーを書き、前のすべての店舗が sfence
後に店舗を許可する前に、世界的に見える sfence
実行を開始します。
x86では、いかなる種類のフェンスもめったに必要ありません。書き込み記憶や非同時期の指示を使用していない限り、必要ではありません。通常、X86は、すべての店舗がプログラムの順序で見えることを保証しますが、WC(書き込み結合)メモリまたは明示的な弱く順序付けられたストアを行う「非同時期の」指示に対してその保証は行われません。 movnti
.
そのため、要約すると、特別な弱い注文された店舗を使用したり、WCメモリタイプにアクセスしたりしない限り、店舗は常にプログラムの順序で表示されます。のようなロックされた命令を使用したアルゴリズム xchg
, 、 また xadd
, 、 また cmpxchg
, 、など、ロックされた命令が連続的に一貫しているため、フェンスなしで機能します。
NTストアを使用している場合は、必要になる場合があります _mm_sfence
または多分さえ _mm_mfence
. 。のユースケース _mm_lfence
はるかにあいまいです。
そうでない場合は、C ++ 11 STD :: Atomicを使用して、コンパイラにメモリ順序を制御するASMの詳細について心配させます。
X86には強く注文されたメモリモデルがありますが、C ++には非常に弱いメモリモデルがあります(Cでは同じ)。 セマンティクスを取得/リリースするには、防止するだけです コンパイル時間 並べ替え. 。ジェフ・プレスシングを参照してください コンパイル時間でのメモリ順序 論文。
_mm_lfence
と _mm_sfence
必要なコンパイラバリア効果を持っていますが、コンパイラに役に立たないものになります lfence
また sfence
コードの実行を遅くするASM命令。
コンパイル時間の並べ替えを制御するためのより良いオプションがあります。 sfence
.
たとえば、GNU C/C ++ asm("" ::: "memory")
コンパイラバリアです(すべての値は、抽象マシンと一致するメモリ内でなければなりません。 "memory"
Clobber)、しかしASM命令は放出されません。
C ++ 11 std :: Atomicを使用している場合、単にできる shared_var.store(tmp, std::memory_order_release)
. 。それはグローバルに見えるようになることが保証されています 後 以前のc割り当ては、非原子変数への割り当てです。
_mm_mfence
は 潜在的に C11 / C ++ 11の独自のバージョンを転がしている場合に便利です std::atomic
, 、実際に mfence
命令は、連続的な一貫性を取得する1つの方法です。つまり、前の店舗がグローバルに見えるようになるまで、後の負荷が値の読み取りを停止することです。ジェフ・プレスシングを参照してください 行為に巻き込まれた記憶の並べ替え.
しかし、それに注意してください mfence
ロックされたAtomic-RMW操作を使用するよりも、現在のハードウェアでは遅いようです。例えば xchg [mem], eax
また、完全な障壁ですが、より速く走り、店を行います。 Skylakeで、方法 mfence
実装されていると、それに続く非メモリ命令の秩序外の実行を防ぎます。見る この答えの底.
ただし、インラインASMのないC ++では、メモリバリアのオプションはより制限されています(X86 CPUにはいくつのメモリバリアがありますか?). mfence
ひどいものではなく、GCCとClangが現在シーケンシャルコンシンジェンシーストアを行うために使用しているものです。
ただし、可能であれば、C ++ 11 STD :: AtomicまたはC11 STDaticを真剣に使用してください。使いやすく、多くのことに対して非常に優れたコードジェンを取得できます。または、Linuxカーネルには、必要な障壁のインラインASMのラッパー関数がすでにあります。時にはそれが単なるコンパイラの障壁であることもありますが、時にはそれはデフォルトよりもランタイムの注文を強くするためのASM命令でもあります。 (例えば、完全な障壁の場合)。
店舗が他のスレッドに速く見えるようにする障壁はありません。彼らができることは、以前のことが起こるまで、現在のスレッドの後の操作を遅らせることです。 CPUはすでに、できるだけ早くL1Dキャッシュに保留中の非特徴的なストアをコミットしようとしています。
_mm_sfence
C ++で実際に手動で使用する最も可能性の高い障壁です
の主なユースケース _mm_sfence()
いくつかの後です _mm_stream
他のスレッドがチェックするフラグを設定する前に、ストア。
見る MEMCPY用の拡張REP MOVSB NTストアと通常の店舗、およびX86メモリ帯域幅の詳細については。非常に大きなバッファー(L3キャッシュサイズよりも大きい)を書くために 絶対 すぐに読み直されることはありません。NTストアを使用することをお勧めします。
NTストアは、通常の店とは異なり、弱い順序であるため、必要です sfence
もしも データを別のスレッドに公開することを気にします。 そうでない場合(最終的にはこのスレッドからそれらを読みます)、そうではありません。または、別のスレッドにデータの準備が整っていることを伝える前にシステムを呼び出す場合、それもシリアル化です。
sfence
NTストアを使用するときに同期をリリース/取得するには、(または他の障壁)が必要です。 C ++ 11 std::atomic
実装はあなたのntストアをフェンスするためにあなたに任されています, 、そのため、アトミックリリースストアが効率的になります。
#include <atomic>
#include <immintrin.h>
struct bigbuf {
int buf[100000];
std::atomic<unsigned> buf_ready;
};
void producer(bigbuf *p) {
__m128i *buf = (__m128i*) (p->buf);
for(...) {
...
_mm_stream_si128(buf, vec1);
_mm_stream_si128(buf+1, vec2);
_mm_stream_si128(buf+2, vec3);
...
}
_mm_sfence(); // All weakly-ordered memory shenanigans stay above this line
// So we can safely use normal std::atomic release/acquire sync for buf
p->buf_ready.store(1, std::memory_order_release);
}
その後、消費者は安全に行うことができます if(p->buf_ready.load(std::memory_order_acquire)) { foo = p->buf[0]; ... }
データレースの未定義の動作なし。読者側はそうします いいえ 必要 _mm_lfence
; NTストアの弱い秩序化された性質は、執筆を行うコアに完全に限定されています。グローバルに見えるようになると、完全に一貫性があり、通常のルールに従って注文されます。
その他のユースケースには注文が含まれます clflushopt
メモリマップされた不揮発性ストレージに保存されているデータの順序を制御します。 (たとえば、オプトンメモリを使用したNVDIMM、またはバッテリー担保DRAMを使用してDIMMが存在するようになりました。)
_mm_lfence
実際の負荷フェンスとしてほとんど役に立つことはありません. 。荷重は、ビデオRAMのようなWC(書き込みコビニング)メモリ領域からロードするときにのみ弱く順序付けます。平 movntdqa
(_mm_stream_load_si128
)通常の(wb = write-back)メモリで強く注文されており、キャッシュ汚染を減らすために何もしません。 (prefetchnta
かもしれませんが、チューニングするのは難しく、事態を悪化させることができます。)
TL:DR:グラフィックドライバーやビデオラムを直接マッピングする他の何かを書いていない場合、必要ありません _mm_lfence
負荷を注文します。
lfence
退職するまで、後の指示の実行を防ぐという興味深い微小構造効果があります。停止する例 _rdtsc()
以前の作業がまだマイクロバンチマークで保留されている間、サイクルカウンターを読むことから。 (常にIntel CPUに適用されますが、MSR設定でのみAMDに適用されます。 lfenceはAMDプロセッサでシリアル化していますか?. 。さもないと lfence
ブルドーザーファミリーで1クロックあたり4回の実行なので、明らかにシリアル化しません。)
C/C ++の内因性を使用しているため、コンパイラはあなたのためにコードを生成しています。あなたはASMを直接制御していませんが、おそらく使用するかもしれません _mm_lfence
Specter緩和のようなものについては、コンパイラをASM出力の適切な場所に置くことができれば、条件付きブランチの直後、ダブル配列アクセスの直前です。 (お気に入り foo[bar[i]]
)。スペクターにカーネルパッチを使用している場合、カーネルは他のプロセスからプロセスを守ると思います。そのため、JITサンドボックスを使用し、それ自体から攻撃されることを心配しているプログラムでこれについて心配する必要があります。サンドボックス。
あなたがすべてに言及する本質的な呼びかけ 挿入するだけです an sfence
, lfence
また mfence
指示が呼ばれるとき。それで、質問は「フェンスの指示の目的は何ですか」になりますか?
簡単な答えはそれです lfence
完全に役に立たない* と sfence
X86のユーザーモードプログラムのメモリ順序付け目的では、ほぼ完全に役に立たない。一方で、 mfence
完全なメモリの障壁として機能するので、近くにまだあるものがない場合は、障壁が必要な場所でそれを使用できます lock
- 必要なものを提供する診断命令。
長くても短い短い答えは...
lfence
lfence
前に負荷を注文するように文書化されています lfence
後の負荷に関しては、この保証はフェンスのない通常の負荷に対してすでに提供されています。つまり、Intelはすでに「負荷が他の負荷で並べ替えられていない」ことを保証しています。実用的な問題として、これは lfence
秩序外の実行バリアとしてのユーザーモードコードでは、おそらく特定の操作を慎重にタイミングするために役立ちます。
sfence
sfence
同じ方法で店舗を前後に注文するように文書化されています lfence
負荷についてはありますが、ロードと同様に、ほとんどの場合、Intelによってストアの注文がすでに保証されています。そうでない主な興味深いケースは、いわゆる非同時期の店です movntdq
, movnti
, maskmovq
他のいくつかの指示。これらの指示は通常のメモリ順序付けルールによって再生されないため、 sfence
これらの店舗と、相対的な順序を実施したい他の店舗の間。 mfence
この目的のためにも機能しますが、 sfence
より速いです。
mfence
他の2つとは異なり、 mfence
実際に何かをします:それは完全なメモリの障壁として機能し、以前のすべての負荷と店舗が完了するようにします1 後続の負荷またはストアが実行を開始する前に。この答えは短すぎて記憶の壁の概念を完全に説明するには短すぎますが、例は次のとおりです。 Dekkerのアルゴリズム, 、各スレッドが重要なセクションを入力したい場合、場所に保存し、他のスレッドがその場所に何かを保存しているかどうかを確認します。たとえば、スレッド1:
mov DWORD [thread_1_wants_to_enter], 1 # store our flag
mov eax, [thread_2_wants_to_enter] # check the other thread's flag
test eax, eax
jnz retry
; critical section
ここで、x86では、店の間にメモリバリアが必要です(最初のメモリバリアが必要です mov
)、および負荷(2番目 mov
)、それ以外の場合は、X86メモリモデルを使用すると、以前のストアで負荷を再注文できるため、他のスレッドが相手のフラグを読むとゼロが表示される可能性があります。したがって、挿入できます mfence
次のように、連続的な一貫性とアルゴリズムの正しい動作を復元するためのバリア:
mov DWORD [thread_1_wants_to_enter], 1 # store our flag
mfence
mov eax, [thread_2_wants_to_enter] # check the other thread's flag
test eax, eax
jnz retry
; critical section
実際には、あなたは見えません mfence
x86のため、予想されるかもしれません lock-prefixed 命令は同じフルバリエ効果を持っています、そして、これらはしばしば/常に(?)より安いです mfence
.
1 たとえば、負荷は満足しており、店舗はグローバルに見えるようになります(ただし、目に見える効果が「まるで発生したかのように」である限り、異なる方法で実装されます)。