比較したいくつかの選択肢は次のとおりです。
@lukehの回答などのインラインアセンブリ。
__sync_bool_compare_and_swap()
:GNU拡張、gcc / clang / ICCのみ、非推奨、疑似関数。コンパイラはCMPXCHG16B
少なくとも次の命令を発行します。-mcx16
atomic_compare_exchange_weak()
/ : C++11でstrong
行うことを行うC11疑似関数。atomic<>
GNUの場合、これはCMPXCHG16B
gcc 7以降では発行されませんが、代わりに呼び出しますlibatomic
(したがって、リンクする必要があります)。動的にリンクさlibatomic
れると、CPUの機能に基づいて使用する関数のバージョンが決定され、CPUが機能するマシンではCMPXCHG16B
それが使用されます。
どうやらclangはまだまたはのためにインラインになりますCMPXCHG16B
atomic_compare_exchange_weak()
strong
私は機械語を試したことがありませんが、(2)の逆アセンブルを見ると、完璧に見え、(1)がどのようにそれを打ち負かすことができるかわかりません。(私はx86の知識はほとんどありませんが、6502をたくさんプログラムしました。)さらに、アセンブリを回避できる場合は絶対に使用しないように十分なアドバイスがあり、少なくともgcc/clangで回避できます。だから私はリストから(1)を越えることができます。
gccバージョン9.2.120190827(Red Hat 9.2.1-1)(GCC)の(2)のコードは次のとおりです。
Thread 2 "mybinary" hit Breakpoint 1, MyFunc() at myfile.c:586
586 if ( __sync_bool_compare_and_swap( &myvar,
=> 0x0000000000407262 <MyFunc+904>: 49 89 c2 mov %rax,%r10
0x0000000000407265 <MyFunc+907>: 49 89 d3 mov %rdx,%r11
(gdb) n
587 was, new ) ) {
=> 0x0000000000407268 <MyFunc+910>: 48 8b 45 a0 mov -0x60(%rbp),%rax
0x000000000040726c <MyFunc+914>: 48 8b 55 a8 mov -0x58(%rbp),%rdx
(gdb) n
586 if ( __sync_bool_compare_and_swap( &myvar,
=> 0x0000000000407270 <MyFunc+918>: 48 c7 c6 00 d3 42 00 mov $0x42d300,%rsi
0x0000000000407277 <MyFunc+925>: 4c 89 d3 mov %r10,%rbx
0x000000000040727a <MyFunc+928>: 4c 89 d9 mov %r11,%rcx
0x000000000040727d <MyFunc+931>: f0 48 0f c7 8e 70 04 00 00 lock cmpxchg16b 0x470(%rsi)
0x0000000000407286 <MyFunc+940>: 0f 94 c0 sete %al
次に、実際のアルゴで(2)と(3)のハンマーテストを行ったところ、実際のパフォーマンスに違いは見られませんでした。理論的にも、(3)は、追加の関数呼び出しを1回実行するだけのオーバーヘッドと、CASが成功したかどうかに関する分岐など、libatomicラッパー関数での作業があります。
(レイジーダイナミックリンクでは、libatomic関数への最初の呼び出しは、CPUIDを使用してCPUが持っているかどうかを確認するinit関数を実際に実行しますcmpxchg16b
。次に、PLTスタブがジャンプするGOT関数ポインターを更新するため、今後の呼び出しは続行されます。名前のサフィックスlibat_compare_exchange_16_i1
は、関数マルチバージョン化のためのGCCのifuncメカニズムに由来します。サポートなしでCPUで実行すると、共有ライブラリ関数がロックを使用したバージョンに解決されます。)lock cmpxchg16b
i1
cmpxchg16b
私の実際のハンマーテストでは、その関数呼び出しのオーバーヘッドは、ロックフリーメカニズムが保護している機能によって使用されるCPUの量で失われます。__sync
したがって、コンパイラ固有であり、起動が非推奨である関数を使用する理由はわかりません。
.compare_exchange_weak()
これは、Fedora 31のアセンブリをシングルステップで実行することで、それぞれに呼び出されるlibatomicラッパーのアセンブリです。を使用してコンパイルすると-fno-plt
、acallq *__atomic_compare_exchange_16@GOTPCREL(%rip)
が呼び出し元にインライン化され、PLTを回避し、プログラムでCPU検出を早期に実行します。最初の呼び出しではなく、起動時間。
Thread 2 "tsquark" hit Breakpoint 2, 0x0000000000403210 in
__atomic_compare_exchange_16@plt ()
=> 0x0000000000403210 <__atomic_compare_exchange_16@plt+0>: ff 25 f2 8e 02 00 jmpq *0x28ef2(%rip) # 0x42c108 <__atomic_compare_exchange_16@got.plt>
(gdb) disas
Dump of assembler code for function __atomic_compare_exchange_16@plt:
=> 0x0000000000403210 <+0>: jmpq *0x28ef2(%rip) # 0x42c108 <__atomic_compare_exchange_16@got.plt>
0x0000000000403216 <+6>: pushq $0x1e
0x000000000040321b <+11>: jmpq 0x403020
End of assembler dump.
(gdb) s
Single stepping until exit from function __atomic_compare_exchange_16@plt,
...
0x00007ffff7fab250 in libat_compare_exchange_16_i1 () from /lib64/libatomic.so.1
=> 0x00007ffff7fab250 <libat_compare_exchange_16_i1+0>: f3 0f 1e fa endbr64
(gdb) disas
Dump of assembler code for function libat_compare_exchange_16_i1:
=> 0x00007ffff7fab250 <+0>: endbr64
0x00007ffff7fab254 <+4>: mov (%rsi),%r8
0x00007ffff7fab257 <+7>: mov 0x8(%rsi),%r9
0x00007ffff7fab25b <+11>: push %rbx
0x00007ffff7fab25c <+12>: mov %rdx,%rbx
0x00007ffff7fab25f <+15>: mov %r8,%rax
0x00007ffff7fab262 <+18>: mov %r9,%rdx
0x00007ffff7fab265 <+21>: lock cmpxchg16b (%rdi)
0x00007ffff7fab26a <+26>: mov %r9,%rcx
0x00007ffff7fab26d <+29>: xor %rax,%r8
0x00007ffff7fab270 <+32>: mov $0x1,%r9d
0x00007ffff7fab276 <+38>: xor %rdx,%rcx
0x00007ffff7fab279 <+41>: or %r8,%rcx
0x00007ffff7fab27c <+44>: je 0x7ffff7fab288 <libat_compare_exchange_16_i1+56>
0x00007ffff7fab27e <+46>: mov %rax,(%rsi)
0x00007ffff7fab281 <+49>: xor %r9d,%r9d
0x00007ffff7fab284 <+52>: mov %rdx,0x8(%rsi)
0x00007ffff7fab288 <+56>: mov %r9d,%eax
0x00007ffff7fab28b <+59>: pop %rbx
0x00007ffff7fab28c <+60>: retq
End of assembler dump.
(2)を使用することで私が見つけた唯一の利点は、マシンにlibatomic
(古いRed Hatの場合はtrue)が付属しておらず、システム管理者にこれを提供するように要求する機能がないか、信頼したくない場合です。彼らは正しいものをインストールします。私は個人的にソースコードで1つをダウンロードし、それを誤ってビルドしたため、16バイトのスワップはミューテックスを使用することになりました:災害。
私は試していません(4)。むしろ、私は始めましたが、gccがコメントなしで渡したコードに関する非常に多くの警告/エラーのため、予算内でコンパイルすることができませんでした。
オプション2、3、および4は同じコードまたはほぼ同じコードで機能するように見えますが、実際には3つすべてでチェックと警告が大幅に異なり、3つのうちの1つが正常にコンパイルされていても、警告なしで-Wall
、他のオプションのいずれかを試してみると、さらに多くの警告またはエラーが発生する可能性があります。疑似関数は__sync*
十分に文書化されていません。(実際、ドキュメントには1/2/4/8バイトしか記載されておらず、16バイトで機能するわけではありません。一方、関数テンプレートのように「一種」で機能しますが、テンプレートが表示されず、 1番目と2番目のarg型は、実際にはそうでatomic_*
はない方法で同じ型です。)要するに、2、3、および4を比較するのは3分の仕事ではありませんでした。