改善从InterlockedCompareExchange()的原子读取

时间:2018-11-22 07:56:38

标签: c++ multithreading 64-bit atomicity interlocked

假定体系结构是ARM64或x86-64。

我想确定这两个是否相等:

  1. a = _InterlockedCompareExchange64((__int64*)p, 0, 0);
  2. MyBarrier(); a = *(volatile __int64*)p; MyBarrier();

MyBarrier()是编译器级别的存储屏障(提示),例如__asm__ __volatile__ ("" ::: "memory")。 因此,方法2应该比方法1更快。

我听说_Interlocked()函数还将暗示编译器和硬件级别的内存障碍。

我听说在这些体系结构上读取(适当对齐的)固有数据是原子的,但是我不确定方法2是否可以广泛使用?

(ps。因为我认为CPU将自动处理数据依赖关系,所以这里不考虑硬件障碍。)

感谢您对此进行任何建议 / 更正


以下是常春藤桥(i5笔记本电脑)上的一些基准测试。

(1E + 006循环: 27毫秒):

; __int64 a = _InterlockedCompareExchange64((__int64*)p, 0, 0);
xor eax, eax
lock cmpxchg QWORD PTR val$[rsp], rbx

(1E + 006循环: 27毫秒):

; __faststorefence(); __int64 a = *(volatile __int64*)p;
lock or DWORD PTR [rsp], 0
mov rcx, QWORD PTR val$[rsp]

(1E + 006循环: 7毫秒):

; _mm_sfence(); __int64 a = *(volatile __int64*)p;
sfence
mov rcx, QWORD PTR val$[rsp]

(1E + 006循环: 1.26ms ,未同步?):

; __int64 a = *(volatile __int64*)p;
mov rcx, QWORD PTR val$[rsp]

1 个答案:

答案 0 :(得分:1)

要使第二个版本在功能上等效,您显然需要原子64位读取,这在您的平台上是正确的。

但是,_MemoryBarrier()不是“编译器提示”。 x86上的_MemoryBarrier()可以防止编译器和CPU重新排序,并且还可以确保写入后具有全局可见性。您可能还只需要第一个_MemoryBarrier(),第二个可以用_ReadWriteBarrier()替换,除非a也是一个共享变量-但由于您正在阅读,因此甚至不需要通过易失性指针,这将防止任何编译器在MSVC中重新排序。

创建此替换项时,基本上会得到几乎same result

// a = _InterlockedCompareExchange64((__int64*)&val, 0, 0);
xor eax, eax
lock cmpxchg QWORD PTR __int64 val, r8 ; val

// _MemoryBarrier(); a = *(volatile __int64*)&val;
lock or DWORD PTR [rsp], r8d
mov rax, QWORD PTR __int64 val ; val

在我的i7 Ivy Bridge笔记本电脑上以循环方式运行这两个程序,结果相等,误差在2-3%之内。

但是,有两个内存障碍,“优化版本”实际上要慢2倍左右。

因此,更好的问题是:您为什么要使用_InterlockedCompareExchange64?如果您需要对变量的原子访问,请使用std::atomic,并且优化编译器应进行编译使其成为您的体系结构最优化的版本,并添加所有必要的障碍以防止重新排序并确保缓存一致性。