ロックフリーのデータ構造とタイミングコードを実装する場合、コンパイラの最適化を抑制することが必要になることがよくあります。通常、これはasm volatile
を使用してclobberリストにmemory
を使用してこれを行いますが、asm volatile
またはプレーンasm
のクラバリングメモリのみが表示される場合があります。
これらの異なるステートメントは、コード生成にどのような影響を及ぼしますか(特にGCCでは、移植性が低いため)。
参考までに、これらは興味深いバリエーションです。
asm (""); // presumably this has no effect on code generation
asm volatile ("");
asm ("" ::: "memory");
asm volatile ("" ::: "memory");
GCCドキュメントの "Extended Asm"ページ を参照してください。
asm
の後にキーワードvolatile
を記述することにより、asm
命令が削除されないようにすることができます。 [...]volatile
キーワードは、命令に重要な副作用があることを示します。 GCCは、到達可能な場合、volatile
asmを削除しません。
そして
出力オペランドのない
asm
命令は、volatileasm
命令と同様に扱われます。
どの例にも出力オペランドが指定されていないため、asm
およびasm volatile
フォームは同じように動作します:コード内に削除できないポイントを作成します(到達不能であることが証明されない限り)。
これは何もしないこととまったく同じではありません。コード生成を変更するダミーasm
の例については この質問 を参照してください-その例では、ループを1000回ループするコードは、ループの16回の繰り返しを計算するコードにベクトル化されますすぐに;ただし、ループ内にasm
が存在すると、最適化が抑制されます(asm
に1000回到達する必要があります)。
"memory"
clobberは、GCCがasm
ブロックによって任意のメモリが任意に読み書きできると想定するため、コンパイラがロードまたはストア全体の順序を変更するのを防ぎます。
これにより、GCCはアセンブラ命令全体でレジスタにキャッシュされたメモリ値を保持せず、そのメモリへのストアまたはロードを最適化しません。
(ただし、CPUが別のCPUに関してロードとストアを並べ替えるのを防ぐことはできませんが、そのためには実際のメモリバリア命令が必要です。)
asm ("")
は何もしません(少なくとも、何もしないはずです)。
asm volatile ("")
も何もしません。
asm ("" ::: "memory")
は、単純なコンパイラフェンスです。
asm volatile ("" ::: "memory")
AFAIKは前と同じです。 volatile
キーワードは、このAssemblyブロックを移動できないことをコンパイラーに伝えます。たとえば、すべての呼び出しで入力値が同じであるとコンパイラが判断した場合、ループから引き上げられます。コンパイラがどのような条件下でアセンブリの配置を最適化しようとするアセンブリについて十分に理解していると判断するかはわかりませんが、volatile
キーワードはそれを完全に抑制します。とはいえ、コンパイラーが宣言された入力または出力を持たないasm
ステートメントを移動しようとした場合、非常に驚くでしょう。
ちなみに、volatile
は、出力値が未使用であると判断した場合、コンパイラーが式を削除できないようにします。ただし、これは出力値がある場合にのみ発生する可能性があるため、asm ("" ::: "memory")
には適用されません。
リリーバラードの答え の完全性のために、Visual Studio 2010は同じことを行う_ReadBarrier()
、_WriteBarrier()
および_ReadWriteBarrier()
を提供しています(VS2010 tは64ビットアプリのインラインアセンブリを許可します)。
これらは命令を生成しませんが、コンパイラの動作に影響します。良い例は here です。
MemoryBarrier()
はlock or DWORD PTR [rsp], 0
を生成します