質問

「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 たとえば、負荷は満足しており、店舗はグローバルに見えるようになります(ただし、目に見える効果が「まるで発生したかのように」である限り、異なる方法で実装されます)。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top