x86 中的原子测试和设置:内联 asm 或编译器生成的锁 bts?

2023-12-04

以下代码在为 xeon phi 编译时会抛出异常Error: cmovc is not supported on k1om.

但它确实可以为常规至强处理器正确编译。

#include<stdio.h>
int main()
{
    int in=5;
    int bit=1;
    int x=0, y=1;
    int& inRef = in;
    printf("in=%d\n",in);
    asm("lock bts %2,%0\ncmovc %3,%1" : "+m" (inRef), "+r"(y) : "r" (bit), "r"(x));
    printf("in=%d\n",in);
}

编译器-icc (ICC) 13.1.0 20130121

相关问题:对 tbb 原子变量进行位测试和设置 (BTS)


IIRC,第一代 Xeon Phi 基于 P5 内核(Pentium 和 Pentium MMX)。cmov直到 P6(又名 Pentium Pro)才推出。所以我认为这是正常的。

只需让编译器通过编写普通的三元运算符来完成其工作即可。

Second, cmov是一个比这更糟糕的选择setc,因为您想根据进位标志生成 0 或 1。请参阅下面我的 asm 代码。

另请注意bts使用内存操作数是非常慢的,所以你不希望它生成该代码,尤其是。在将 x86 指令解码为 uop 的 CPU 上(如现代 Xeon)。根据http://agner.org/optimize/, bts m, rbts m, i即使在 P5 上,也不要这样做。

只需询问编译器即可in要在寄存器中,或者更好的是,不要为此使用内联汇编。


由于 OP 显然希望它能够自动工作,所以最好的解决方案是使用 C++11std::atomic::fetch_or,并将其留给编译器来生成lock bts.

std::atomic_flag has a test_and_set功能,但不知道是否有办法将它们紧密地包装在一起。也许作为结构中的位域?不过不太可能。我也没有看到原子操作std::bitset.

不幸的是,当前版本的 gcc 和 clang 不会生成lock bts from fetch_or,即使可以使用更快的立即操作数形式。我想出了以下内容(神螺栓链接):

#include <atomic>
#include <stdio.h>

// wastes instructions when the return value isn't used.
// gcc 6.0 has syntax for using flags as output operands

// IDK if lock BTS is better than lock cmpxchg.
// However, gcc doesn't use lock BTS even with -Os
int atomic_bts_asm(std::atomic<unsigned> *x, int bit) {
  int retval = 0;  // the compiler still provides a zeroed reg as input even if retval isn't used after the asm :/
  // Letting the compiler do the xor means we can use a m constraint, in case this is inlined where we're storing to already zeroed memory
  // It unfortunately doesn't help for overwriting a value that's already known to be 0 or 1.
  asm( // "xor      %[rv], %[rv]\n\t"
       "lock bts %[bit], %[x]\n\t"
       "setc     %b[rv]\n\t"  // hope that the compiler zeroed with xor to avoid a partial-register stall
        : [x] "+m" (*x), [rv] "+rm"(retval)
        : [bit] "ri" (bit));
  return retval;
}

// save an insn when retval isn't used, but still doesn't avoid the setc
// leads to the less-efficient setc/ movzbl sequence when the result is needed :/
int atomic_bts_asm2(std::atomic<unsigned> *x, int bit) {
  uint8_t retval;
  asm( "lock bts %[bit], %[x]\n\t"
       "setc     %b[rv]\n\t"
        : [x] "+m" (*x), [rv] "=rm"(retval)
        : [bit] "ri" (bit));
  return retval;
}


int atomic_bts(std::atomic<unsigned> *x, unsigned int bit) {
  // bit &= 31; // stops gcc from using shlx?
  unsigned bitmask = 1<<bit;
  //int oldval = x->fetch_or(bitmask, std::memory_order_relaxed);

  int oldval = x->fetch_or(bitmask, std::memory_order_acq_rel);
  // acquire and release semantics are free on x86
  // Also, any atomic rmw needs a lock prefix, which is a full memory barrier (seq_cst) anyway.

  if (oldval & bitmask)
    return 1;
  else
    return 0;
}

正如中所讨论的在 x86 汇编中将寄存器设置为零的最佳方法是什么:xor、mov 或 and?, xor/设置标志/setc当需要结果为 0 或 1 值时,这是所有现代 CPU 的最佳序列。我实际上还没有考虑过 P5,但是setccP5 速度很快,所以应该没问题。

当然,如果你想对此进行分支而不是存储它,那么内联汇编和 C 之间的边界是一个障碍。花费两条指令来存储 0 或 1,只是为了对其进行测试/分支,这是非常愚蠢的。

如果可以的话,gcc6 的标志操作数语法当然值得研究。 (如果您需要一个针对 Intel MIC 的编译器,则可能不需要。)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

x86 中的原子测试和设置:内联 asm 或编译器生成的锁 bts? 的相关文章

随机推荐