9

理由はわかりませんが、これは正確には正しくないようです。CMPXCHG16Bのドキュメントはごくわずかなので、アドバイスは素晴らしいでしょう(私はインテルのマニュアルを持っていません...)

template<>
inline bool cas(volatile types::uint128_t *src, types::uint128_t cmp, types::uint128_t with)
{
    /*
    Description:
     The CMPXCHG16B instruction compares the 128-bit value in the RDX:RAX and RCX:RBX registers 
     with a 128-bit memory location. If the values are equal, the zero flag (ZF) is set, 
     and the RCX:RBX value is copied to the memory location. 
     Otherwise, the ZF flag is cleared, and the memory value is copied to RDX:RAX.
     */
    uint64_t * cmpP = (uint64_t*)&cmp;
    uint64_t * withP = (uint64_t*)&with;
    unsigned char result = 0;
    __asm__ __volatile__ (
    "LOCK; CMPXCHG16B %1\n\t"
    "SETZ %b0\n\t"
    : "=q"(result)  /* output */ 
    : "m"(*src), /* input */
      //what to compare against
      "rax"( ((uint64_t) (cmpP[1])) ), //lower bits
      "rdx"( ((uint64_t) (cmpP[0])) ),//upper bits
      //what to replace it with if it was equal
      "rbx"( ((uint64_t) (withP[1])) ), //lower bits
      "rcx"( ((uint64_t) (withP[0]) ) )//upper bits
    : "memory", "cc", "rax", "rdx", "rbx","rcx" /* clobbered items */
    );
    return result;
}

例を使用して実行すると、1になるはずのときに0が返されます。何かアイデアはありますか?

4

5 に答える 5

13

いくつかの問題に気づきました、

(1)主な問題は制約です。「rax」は見た目どおりに機能せず、最初の文字「r」によってgccが任意のレジスタを使用できるようになります。

(2)格納タイプ:: uint128_tがわからないが、x86プラットフォームの標準のリトルエンディアンを想定すると、高位と低位のdwordも入れ替わります。

(3)何かのアドレスを取得し、それを他の何かにキャストすると、エイリアシングルールに違反する可能性があります。types :: uint128_tがどのように定義されているかによって、これが問題になるかどうかによって異なります(2つのuint64_tの構造体の場合は問題ありません)。-O2を指定したGCCは、エイリアシングルールに違反していないと想定して最適化します。

(4)* srcは、メモリクローバーを指定するのではなく、実際には出力としてマークする必要があります。しかし、これは実際には、正確性の問題というよりもパフォーマンスの問題です。同様に、rbxとrcxをclobberedとして指定する必要はありません。

これが機能するバージョンです、

#include <stdint.h>

namespace types
{
    // alternative: union with  unsigned __int128
    struct uint128_t
    {
        uint64_t lo;
        uint64_t hi;
    }
    __attribute__ (( __aligned__( 16 ) ));
}

template< class T > inline bool cas( volatile T * src, T cmp, T with );

template<> inline bool cas( volatile types::uint128_t * src, types::uint128_t cmp, types::uint128_t with )
{
    // cmp can be by reference so the caller's value is updated on failure.

    // suggestion: use __sync_bool_compare_and_swap and compile with -mcx16 instead of inline asm
    bool result;
    __asm__ __volatile__
    (
        "lock cmpxchg16b %1\n\t"
        "setz %0"       // on gcc6 and later, use a flag output constraint instead
        : "=q" ( result )
        , "+m" ( *src )
        , "+d" ( cmp.hi )
        , "+a" ( cmp.lo )
        : "c" ( with.hi )
        , "b" ( with.lo )
        : "cc", "memory" // compile-time memory barrier.  Omit if you want memory_order_relaxed compile-time ordering.
    );
    return result;
}

int main()
{
    using namespace types;
    uint128_t test = { 0xdecafbad, 0xfeedbeef };
    uint128_t cmp = test;
    uint128_t with = { 0x55555555, 0xaaaaaaaa };
    return ! cas( & test, cmp, with );
}
于 2011-01-28T16:12:21.223 に答える
6

すべてのインテルのドキュメントは無料で入手できます:インテル®64およびIA-32アーキテクチャーソフトウェア開発者用マニュアル

于 2011-01-28T07:54:06.837 に答える
3

GCCを使用している場合は、この命令を取得するためにインラインasmを使用する必要がないことに注意してください。次のような__sync関数の1つを使用できます。

template<>
inline bool cas(volatile types::uint128_t *src,
                types::uint128_t cmp,
                types::uint128_t with)
{
    return __sync_bool_compare_and_swap(src, cmp, with);
}

Microsoftには、VC++に対して同様の機能があります。

__int64 exchhi = __int64(with >> 64);
__int64 exchlo = (__int64)(with);

return _InterlockedCompareExchange128(a, exchhi, exchlo, &cmp) != 0;
于 2014-08-10T04:54:01.977 に答える
1

比較したいくつかの選択肢は次のとおりです。

  1. @lukehの回答などのインラインアセンブリ。

  2. __sync_bool_compare_and_swap()GNU拡張、gcc / clang / ICCのみ、非推奨、疑似関数。コンパイラはCMPXCHG16B少なくとも次の命令を発行します。-mcx16

  3. atomic_compare_exchange_weak()/ : C++11でstrong行うことを行うC11疑似関数。atomic<>GNUの場合、これはCMPXCHG16Bgcc 7以降では発行されませんが、代わりに呼び出しますlibatomic(したがって、リンクする必要があります)。動的にリンクさlibatomicれると、CPUの機能に基づいて使用する関数のバージョンが決定され、CPUが機能するマシンではCMPXCHG16Bそれが使用されます。

  4. どうやらclangはまだまたはのためにインラインになりますCMPXCHG16Batomic_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 cmpxchg16bi1cmpxchg16b

私の実際のハンマーテストでは、その関数呼び出しのオーバーヘッドは、ロックフリーメカニズムが保護している機能によって使用される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分の仕事ではありませんでした。

于 2020-05-13T01:40:43.643 に答える
0

わずかな変更を加えてg++用にコンパイルしました(cmpxchg16b命令のoword ptrを削除しました)。しかし、私は間違っているかもしれませんが、必要に応じてメモリを上書きしていないようです。 [更新を参照]コードを以下に示し、その後に出力を示します。

#include <stdint.h>
#include <stdio.h>

namespace types
{
  struct uint128_t
  {
    uint64_t lo;
    uint64_t hi;
  }
  __attribute__ (( __aligned__( 16 ) ));
 }

 template< class T > inline bool cas( volatile T * src, T cmp, T with );

 template<> inline bool cas( volatile types::uint128_t * src, types::uint128_t cmp,  types::uint128_t with )
 {
   bool result;
   __asm__ __volatile__
   (
    "lock cmpxchg16b %1\n\t"
    "setz %0"
    : "=q" ( result )
    , "+m" ( *src )
    , "+d" ( cmp.hi )
    , "+a" ( cmp.lo )
    : "c" ( with.hi )
    , "b" ( with.lo )
    : "cc"
   );
   return result;
}

void print_dlong(char* address) {

  char* byte_array = address;
  int i = 0;
  while (i < 4) {
     printf("%02X",(int)byte_array[i]);
     i++;
  }

  printf("\n");
  printf("\n");

}

int main()
{
  using namespace types;
  uint128_t test = { 0xdecafbad, 0xfeedbeef };
  uint128_t cmp = test;
  uint128_t with = { 0x55555555, 0xaaaaaaaa };

  print_dlong((char*)&test);
  bool result = cas( & test, cmp, with );
  print_dlong((char*)&test);

  return result;
}

出力

FFFFFFADFFFFFFFBFFFFFFCAFFFFFFDE


55555555

出力が私にとって意味があるかどうかわかりません。構造体の定義によれば、beforeの値は00000000decafbad00000feedbeefのようなものになると期待していました 。しかし、バイトは単語の中に広がっているようです。それは整列されたディレクティブによるものですか?ところで、CAS操作は正しい戻り値を返すようです。これを解読するのに何か助けはありますか?

更新:gdbを使用したメモリ検査でデバッグを行いました。そこに正しい値が表示されます。したがって、これは私のprint_dlongプロシージャの問題であるに違いないと思います。お気軽に修正してください。これの修正されたバージョンは、印刷された結果でcas操作を説明するので、私はこの返信を修正するために残しておきます。

于 2014-08-10T04:33:57.743 に答える