改进来自 InterlockedCompareExchange() 的原子读取

Posted

技术标签:

【中文标题】改进来自 InterlockedCompareExchange() 的原子读取【英文标题】:improve atomic read from InterlockedCompareExchange() 【发布时间】:2018-11-22 07:56:37 【问题描述】:

假设架构是 ARM64 或 x86-64。

我想确定这两者是否等价:

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

其中MyBarrier() 是编译器级别的内存屏障(提示),如__asm__ __volatile__ ("" ::: "memory")。 所以方法2应该比方法1快。

我听说_Interlocked() 函数也会暗示编译器和硬件级别的内存屏障。

我听说读取(正确对齐的)内在数据在这些架构上是原子的,但我不确定方法 2 是否可以广泛使用?

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

感谢您对此提出任何建议/更正


这是 Ivy Bridge(i5 笔记本电脑)上的一些基准测试。

(1E+006 次循环:27ms):

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

(1E+006 次循环:27ms):

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

(1E+006 次循环:7ms):

; _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]

【问题讨论】:

只是不等价。 sfence 确保商店是可见的,但不确保负载是新鲜的。所以根本没有原子读取。 mfence 是等效的,它很可能不再有任何区别。也许你的意思是 lfence,很难说。 【参考方案1】:

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

但是,_MemoryBarrier() 不是“对编译器的提示”。 x86 上的_MemoryBarrier() 可防止编译器和 CPU 重新排序,并确保写入后的全局可见性。您也可能只需要第一个 _MemoryBarrier(),第二个可以替换为 _ReadWriteBarrier(),除非 a 也是一个共享变量 - 但您甚至不需要它,因为您正在通过 volatile 指针读取,它将阻止 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,优化编译器应该将它编译到最为您的架构优化版本,并添加所有必要的障碍以防止重新排序并确保缓存一致性。

【讨论】:

顺便说一句,__int64?您应该坚持使用来自 stdint.h/cstdint 的标准 typedef。 很抱歉,我之前使用了误导性的_MemoryBarrier() 而不是MyBarrier()。我没有使用微软的宏 Me​​moryBarrier()。因此,第二版(“优化版”)的更新后的 asm 代码不应包含由MemoryBarrier() 发出的lock or DWORD PTR [rsp], r8d 联锁功能很容易理解。而且我个人讨厌使用std::atomic,这对我来说太复杂了。 @cozmoz:在这种情况下,生成的代码不能保证其他线程会看到按程序顺序更新的值。无论如何,作为一名 C++ 程序员,你真的应该花点时间阅读std::atomic 的文档。它是标准的、有效的,而且最重要的是,它可以让您明确地传达您的意图。您只需要原子读取吗?使用memory_order_relaxed。您是否需要以顺序一致性发布所有线程的更改?使用memory_order_seq_cst。现在,您将性能优化置于代码正确性和清晰度之上。 谢谢。我总是使用互锁函数来修改共享变量,所以生产者线程没有问题。由于 ARM64/x86-64 的原子性从来都不是问题,因此唯一需要的是在消费者线程中读取真实值。问题是,如果变量被某个生产者线程中的某个互锁函数修改,更新后的值是否会通过简单的 volatile 读取在另一个查看器线程中立即可见?

以上是关于改进来自 InterlockedCompareExchange() 的原子读取的主要内容,如果未能解决你的问题,请参考以下文章

如何改进 CNN 模型?

NetworkManager 1.38 改进了对IPv6 和 Wi-Fi 热点的支持

敏捷开发的持续改进

“基于灰色关联度的改进BP神经网络算法”,带你领略光伏功率预测精准之风

改进 open cv 中的 camshift 算法

除了性能改进之外,如何最好地利用电容器的引入?