【问题标题】:Atomic test-and-set in x86: inline asm or compiler-generated lock bts?x86 中的原子测试和设置:内联 asm 还是编译器生成的锁 bts?
【发布时间】:2016-01-22 06:29:40
【问题描述】:

以下代码在为 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

相关问题:bit test and set (BTS) on a tbb atomic variable

【问题讨论】:

  • IIRC,第一代 Xeon Phi 基于 P5 内核(Pentium 和 Pentium MMX)。 cmov 直到 P6(又名 Pentium Pro)才推出。所以我觉得这很正常。只需让编译器通过编写一个普通的三元运算符来完成它的工作。另请注意,带有内存操作数的bts 非常慢。不要那样做。另外,我认为您正在测试in 地址中的位,因为您要求将&amp;in 存储在内存中。这是你想要的吗?
  • 我假设 xeon phi 也是基于 x86 的,它应该可以正常工作
  • @PeterCordes:是的,bts 很慢。我在多线程代码中使用它。试图设置整数的特定位。如果 bts 成功设置该位,则变量 y 将为 1。如果该位已设置,则为0
  • 您没有使用lock bts,因此将其与内存操作数一起使用是没有意义的。它仍然是一个单独的读取-修改-写入。使用std::atomicfetch_oren.cppreference.com/w/cpp/atomic/atomic/fetch_or。 (另外,我关于修改地址的评论是错误的。它是一个引用,而不是一个指针。我看不出使用引用的目的,但"+m" 因为它应该仍然与in 所在的内存相同存储。
  • 啊..你是对的。当我剥离代码以创建 MWE 时,我错过了。

标签: assembly x86 icc xeon-phi


【解决方案1】:

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

只需让编译器通过编写一个普通的三元运算符来完成它的工作。

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

另请注意,带有内存操作数的bts 非常慢,因此无论如何您都不希望它生成该代码,尤其是。在将 x86 指令解码为 uops 的 CPU 上(如现代 Xeon)。根据http://agner.org/optimize/ 的说法,即使在 P5 上,bts m, r 也比 bts m, i 慢得多,所以不要那样做。

只需要求编译器将in 放在寄存器中,或者更好的是,不要为此使用内联汇编。


由于 OP 显然希望它以原子方式工作,因此最好的解决方案是使用 C++11 的 std::atomic::fetch_or,并将其留给编译器生成 lock bts

std::atomic_flag 有一个 test_and_set 函数,但如果有办法将它们紧紧地打包,IDK。也许作为结构中的位域?不过不太可能。我也没有看到 std::bitset 的原子操作。

不幸的是,当前版本的 gcc 和 clang 不会从 fetch_or 生成 lock bts,即使可以使用更快的立即操作数形式也是如此。我想出了以下(godbolt link):

#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;
}

正如What is the best way to set a register to zero in x86 assembly: xor, mov or and? 中所讨论的,xor / set-flags / setc 是所有现代 CPU 的最佳序列,当需要将结果作为 0 或 1 值时。我实际上并没有考虑过 P5,但setcc 在 P5 上很快,所以应该没问题。

当然,如果你想在 this 上分支而不是存储它,inline asm 和 C 之间的边界是一个障碍。花费两条指令来存储 0 或 1,只在其上进行测试/分支,这将是非常愚蠢的。

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

【讨论】:

  • bts m,rbts m,i 有什么区别?这里的i是指立即寻址方式吗?
  • @arunmoezhi:是的,i 是直接的。如果位位置是编译时常量,那么您将获得更好的结果,因为 CPU 在流水线中更早地知道最终内存地址,因此它解码为更少的微指令。例如,使用ri 约束。我没有太多运气让 gcc 生成 lock bts,即使它可能是最佳的。 goo.gl/yKYTfYen.cppreference.com/w/cpp/atomic/atomic_flag 看起来很有用(具有原子 test_and_set 函数),但仅适用于独立标志,不适用于 int 中的位:/
  • 谢谢。在atomic_bts 函数中,汇编代码有cmpxchg。但在多线程环境中,compare_and_exchange 可能比bts 慢。例如,两个线程AB 尝试分别simultaneously 设置位bitAbitB。如果我使用cmpxchg,那么只有一个会成功。但是这两个位上的bts 都会成功。
  • atomic_flag 看起来是个不错的选择。但我不能使用它,因为它是一个单一的标志。我使用整数作为可以单独锁定的位数组。 atomic_flag 只会给我一把锁。但是一个整数将给出至少 32 位,每个位都可以用作锁
  • @arunmoezhi:所以你创建了一个具有 32 个锁定位的单词。如果你真的需要那么多锁,即使线程需要不同的锁,这也将是一个非常高的争用点。我认为您希望在各自的缓存行中创建一个单独的锁数组,以便特定“位”的竞争者仅竞争该锁的缓存行。 [[IIRC,英特尔有一些有趣的规则,关于锁应该相距多远以最小化总线锁定,它可能是 128 字节、2-4 缓存线或类似的。我建议您查阅英特尔优化手册。]
猜你喜欢
  • 2014-05-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-11
  • 1970-01-01
  • 2011-11-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多