C ++:128位比较和交换的原子性测试

时间:2018-08-01 21:53:55

标签: c++ gcc g++ atomic

我正在尝试验证不同intel CPU上128位操作的原子性,但是由于某种原因,我的程序在我测试的每台机器上都无法通过原子性测试。我已经在常春藤桥和Broadwell CPU上进行了测试。我已经在下面包含了源代码。基本上,它是同时增加两个64位计数器,这些计数器组合在一起形成128位整数。如果原子性在CPU上起作用,则低64位必须始终等于高64位。这就是该程序正在测试的内容。但是测试始终失败,并显示以下输出:

  

发现不一致的地方18550,18551

我正在运行ubuntu 4.15,gcc,我正在将此代码编译为:

g++ -pthread -march=native test.cc -latomic

代码如下:

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

#include <thread>

__int128 __attribute__((__aligned__(16))) count;

void task() {
 while(1) {
  __int128 old_count, swapped_count;
  do {
    __atomic_load(&count, &old_count, __ATOMIC_SEQ_CST);
    uint64_t old_lo = (uint64_t)old_count;
    uint64_t old_hi = ((uint64_t)(old_count >> 64));
    if (old_lo != old_hi) {
      printf("Found inconsistency %lu, %lu\n", old_lo, old_hi);
      exit(1);
    }
    __int128 __attribute__((__aligned__(16))) new_count;
    new_count = old_hi + 1;
    new_count <<= 64;
    new_count |= (old_lo + 1);
    swapped_count = __sync_val_compare_and_swap(&count, old_count, new_count);
    uint64_t new_lo = (uint64_t)swapped_count;
    uint64_t new_hi = (uint64_t)(swapped_count >> 64);
    if (new_lo != new_hi) {
      printf("Found inconsistency post swap %lu, %lu\n", new_lo, new_hi);
      exit(1);
    }
    // At this point count must have changed one way or the other
    if (count == old_count) {
      printf("Count is still the same ????\n");
      exit(1);
    }
  }  while (old_count != swapped_count);
 }  // while (1)
}

int main() {
  count = 0;
  for (int i = 0; i < 2; i++)
    new std::thread(task);

  while(1)
    sleep(1);
}

1 个答案:

答案 0 :(得分:0)

所以我发现gcc不能以无锁方式实现16Byte原子。使用gdb进行调试后,我发现gcc中的__atomic * _16()函数使用了锁。另一方面,__sync * _16()函数使用lock cmpxchg16指令。因此,马克的建议是正确的。您不能将__sync与__atomic混合使用,并且通常__atomic(特别是16字节操作)不是无锁的(还可以吗?)。

因此,我最终实现了针对16字节原子加载和CAS的自己的原语。我在下面粘贴该代码。使用这些原语,上面的代码即可正常工作。

union alignas(16) atomic_u128 {
  unsigned __int128 val;
  struct {
    volatile uint64_t lo, hi;
  };
};

// Atomically read a 128 bit unsigned.
__attribute__((always_inline)) inline unsigned __int128 AtomicLoad128(
    register atomic_u128 *src) {
  atomic_u128 ret;
  __asm__ __volatile__ (
      "xor %%ecx, %%ecx\n"
      "xor %%eax, %%eax\n"
      "xor %%edx, %%edx\n"
      "xor %%ebx, %%ebx\n"
      "lock cmpxchg16b %2"
      : "=&a"(ret.lo), "=d"(ret.hi)
      : "m"(*src)
      : "cc", "rbx", "rcx" );
  return ret.val;
}

__attribute__((always_inline)) inline bool AtomicCAS128(
    volatile atomic_u128 *src, atomic_u128 *expected,
    atomic_u128 desired) {
  bool result;
  atomic_u128 e;
  e.val = expected->val;
  __asm__ __volatile__ (
    "lock cmpxchg16b %1"
    : "=@ccz" ( result ), "+m" ( *src ), "+a"(e.lo), "+d"(e.hi)
    : "c" ( desired.hi ), "b" ( desired.lo )
    : "cc");
  if (!result)
    expected->val = e.val;

  return result;
}