Linuxカーネルはlock; addl $0,0(%%esp)
書き込みバリアとして使用し、RE2ライブラリはxchgl (%0),%0
書き込みバリアとして使用します。違いは何ですか?どちらが良いですか?
x86には読み取りバリア命令も必要ですか?RE2は、読み取りバリア機能をx86でのno-opとして定義しますが、Linuxは、lfence
SSE2が使用可能かどうかに応じて、読み取りバリア機能をno-opまたはno-opとして定義します。いつlfence
必要ですか?
Linuxカーネルはlock; addl $0,0(%%esp)
書き込みバリアとして使用し、RE2ライブラリはxchgl (%0),%0
書き込みバリアとして使用します。違いは何ですか?どちらが良いですか?
x86には読み取りバリア命令も必要ですか?RE2は、読み取りバリア機能をx86でのno-opとして定義しますが、Linuxは、lfence
SSE2が使用可能かどうかに応じて、読み取りバリア機能をno-opまたはno-opとして定義します。いつlfence
必要ですか?
IA32マニュアル(第3A巻、第8.2章:メモリオーダリング)からの引用:
ライトバックキャッシュ可能として定義されたメモリ領域のシングルプロセッサシステムでは、メモリオーダリングモデルは次の原則を尊重します[..]
- 読み取りは他の読み取りと並べ替えられません
- 書き込みは古い読み取りで並べ替えられません
- メモリへの書き込みは、他の書き込みと並べ替えられません。
CLFLUSH
命令で実行された書き込み- 非一時的な移動命令で実行されるストリーミングストア(書き込み)([ここに命令のリスト])
- 文字列操作(セクション8.2.4.1を参照)
- 読み取りは、異なる場所への古い書き込みで並べ替えることができますが、同じ場所への古い書き込みでは並べ替えることができません。
- 読み取りまたは書き込みは、I / O命令、ロックされた命令、またはシリアル化命令で並べ替えることはできません
- 読み取りは合格できず
LFENCE
、MFENCE
指示- 書き込みは通過できず
SFENCE
、MFENCE
指示
注:上記の「シングルプロセッサシステムの場合」は、少し誤解を招く恐れがあります。同じルールが各(論理)プロセッサに個別に適用されます。次に、マニュアルでは、複数のプロセッサ間の追加の順序付け規則について説明します。質問に関連するそれについての唯一のビットはそれです
- ロックされた命令には全順序があります。
要するに、ライトバックメモリ(ドライバーまたはグラフィックプログラマーでない限り、これまでに表示されるすべてのメモリ)に書き込んでいる限り、ほとんどのx86命令はほぼ順次一貫しており、唯一の並べ替えです。 x86 CPUが実行できるのは、書き込みの前に実行するために、後で(独立した)読み取りを並べ替えることです。書き込みバリアの主な点は、lock
プレフィックス(暗黙的または明示的)が付いていることです。これにより、すべての並べ替えが禁止され、マルチプロセッサシステムのすべてのプロセッサで操作が同じ順序で表示されるようになります。
また、ライトバックメモリでは、読み取りが並べ替えられることはないため、読み取りバリアは必要ありません。最近のx86プロセッサは、ストリーミングストアおよび書き込み結合メモリ(マップされたグラフィックメモリに一般的に使用される)のメモリ整合性モデルが弱くなっています。そこが様々なfence
指示が出てきます。他のメモリタイプには必要ありませんが、Linuxカーネルの一部のドライバは書き込み結合メモリを処理するため、読み取りバリアをそのように定義しただけです。メモリタイプごとの順序付けモデルのリストは、Vol。11.3.1にあります。IA-32マニュアルの3A。ショートバージョン:ライトスルー、ライトバック、ライトプロテクトにより投機的読み取りが可能(上記のルールに従う)、キャッシュ不可および強力なキャッシュ不可メモリには強力な順序保証があります(プロセッサの並べ替えなし、読み取り/書き込みはすぐに実行され、MMIOに使用されます) )およびWrite Combinedメモリの順序は弱い(つまり、フェンスを必要とする順序付けルールが緩和されている)。
(%% esp)アドレスでロック変数の0状態をテストする場合、「lock; addl $ 0,0(%% esp) 」の方が高速です。ロック変数に0の値を追加し、アドレス(%% esp)の変数のロック値が0の場合、ゼロフラグが1に設定されるためです。
Intelデータシートからのlfence:
LFENCE命令の前に発行されたすべてのメモリからのロード命令に対してシリアル化操作を実行します。このシリアル化操作により、プログラム順序で先行するすべてのロード命令がグローバルに表示され、その後にLFENCE命令に続くロード命令がグローバルに表示されることが保証されます。
(編集者注:mfence
またはlock
ed操作は、逐次一貫性のための(ストア後の)唯一の有用なフェンスです。 ストアバッファーによるStoreLoadの並べ替えをブロックlfence
しません。)
たとえば、「mov」のようなメモリ書き込み命令は、適切に配置されている場合、アトミックです(ロックプレフィックスは必要ありません)。ただし、この命令は通常CPUキャッシュで実行され、他のすべてのスレッドでは現時点ではグローバルに表示されません。これは、前のストアが他のスレッドに表示されるまでこのスレッドを待機させるために、メモリフェンスを最初に実行する必要があるためです。
したがって、これら2つの命令の主な違いは、xchgl命令が条件付きフラグに影響を与えないことです。確かに、 lock cmpxchg命令を使用してロック変数の状態をテストできますが、これはlock add$0命令を使用する場合よりもさらに複雑です。
lock addl $0, (%esp)
の代わりでmfence
あり、ではありませんlfence
。
ユースケースは、StoreLoadの並べ替え(x86の強力なメモリモデルで許可される唯一の種類)をブロックする必要があるが、共有変数に対するアトミックRMW操作は必要ない場合です。 https://preshing.com/20120515/memory-reordering-caught-in-the-act/
例:整列していると仮定するstd::atomic<int> a,b
:
movl $1, a a = 1; Atomic for aligned a
# barrier needed here
movl b, %eax tmp = b; Atomic for aligned b
オプションは次のとおりです。
xchg
たとえばmov $1, %eax
/を使用して逐次一貫性ストアをxchg %eax, a
実行すると、個別のバリアは必要ありません。それは店の一部です。これは、最新のハードウェアで最も効率的なオプションだと思います。gcc以外のC++11コンパイラxchg
はseq_cstストアに使用します。mfence
バリアとして使用します。(gccはseq_cstストアにmov
+を使用します)。mfence
lock addl $0, (%esp)
バリアとして使用します。edの指示はすべてlock
完全な障壁です。 ロックxchgはmfenceと同じ動作をしますか?
(または他の場所に移動しますが、L1dではスタックはほとんどの場合プライベートでホットなので、ある程度適切な候補です。ただし、これにより、スタックの最下部にあるデータを使用する何かの依存関係チェーンが作成される可能性があります。)
xchg
古い値に依存しない値でメモリ位置を無条件に書き込むため、ストアに折りたたむことによってのみバリアとして使用できます。
可能であれば、xchg
共有ロケーションからも読み取る場合でも、seq-cstストアに使用するのがおそらく最善です。 mfence
最近のIntelCPUでは予想よりも低速であり(再順序付けされる命令はロードとストアのみですか?)、同じ方法で独立した非メモリ命令のアウトオブオーダー実行もブロックしlfence
ます。
利用可能な場合でもlock addl $0, (%esp)/(%rsp)
代わりに使用する価値があるかもしれませんが、私は欠点を実験していません。または何かを使用すると、ホットなもの(ローカルまたはリターンアドレス)へのデータ依存関係が長くなる可能性が低くなる可能性がありますが、それはvalgrindのようなツールを不幸にする可能性があります。mfence
mfence
-64(%rsp)
lfence
MOVNTDQAロードを使用してビデオRAM(または他のWCの弱い順序の領域)から読み取っていない限り、メモリの順序付けには役立ちません。
アウトオブオーダー実行(ストアバッファーではない)のシリアル化は、StoreLoadの並べ替え(x86の強力なメモリモデルで通常のWB(ライトバック)メモリ領域を許可する唯一の種類)を停止するのに役立ちません。
の実際のユースケースlfence
は、コードの非常に短いブロックのタイミングのアウトオブオーダー実行をrdtsc
ブロックするため、または条件付きまたは間接分岐を介して推測をブロックすることによるSpectreの軽減のためです。
なぜ役に立たないのか、および各バリア命令をいつ使用するのかについては、「いつ_mm_sfence _mm_lfenceと_mm_mfence(私の答えと@BeeOnRopeの答え)を使用すべきか」も参照してください。lfence
(または私の場合、asmではなくC++でプログラミングする場合のC++組み込み関数)。
他の回答とは別に、HotSpotの開発者はlock; addl $0,0(%%esp)
、ゼロオフセットでは最適ではない可能性があることを発見しました。一部のプロセッサでは、誤ったデータ依存関係が発生する可能性があります。関連するjdkバグ。
オフセットが異なるスタックの場所に触れると、状況によってはパフォーマンスが向上する可能性があります。
lock; addl
との重要な部分はxchgl
プレフィックスlock
です。は暗黙的ですxchgl
。2つの間に実際には違いはありません。それらがどのようにアセンブルされるかを見て、x86での同等の操作では通常は高速であるため(バイト単位)短いものを選択します(したがって、のようなトリックxorl eax,eax
)
SSE2の存在は、おそらく、最終的にはの関数である実際の状態の単なるプロキシですcpuid
。おそらくlfence
、SSE2は、起動時にSSE2の存在と可用性がチェック/キャッシュされたことを意味していることがわかります。 lfence
利用可能な場合は必須です。