改进来自 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()
。我没有使用微软的宏 MemoryBarrier()。因此,第二版(“优化版”)的更新后的 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() 的原子读取的主要内容,如果未能解决你的问题,请参考以下文章
NetworkManager 1.38 改进了对IPv6 和 Wi-Fi 热点的支持