R. *_*ter 3 interlocked lock-free visual-c++ data-synchronization
在MSVC for x64(19.10.25019)上,
InterlockedOr(&g, 1)
Run Code Online (Sandbox Code Playgroud)
生成此代码序列:
prefetchw BYTE PTR ?g@@3JC
mov eax, DWORD PTR ?g@@3JC ; g
npad 3
$LL3@f:
mov ecx, eax
or ecx, 1
lock cmpxchg DWORD PTR ?g@@3JC, ecx ; g
jne SHORT $LL3@f
Run Code Online (Sandbox Code Playgroud)
我本来期望更简单(和无环):
mov eax, 1
lock or [?g@@3JC], eax
Run Code Online (Sandbox Code Playgroud)
InterlockedAnd生成类似的代码InterlockedOr.
必须为此指令设置循环似乎非常低效.为什么会生成此代码?
(作为旁注:我使用的全部原因是InterlockedOr对变量进行原子加载 - 我已经知道这InterlockedCompareExchange是做到这一点的方法.我很奇怪没有InterlockedLoad(&x),但我离题了... )
记录的合同InterlockedOr使其返回原始值:
InterlockedOr对指定的
LONG值执行原子OR运算.该函数防止多个线程同时使用同一个变量.Run Code Online (Sandbox Code Playgroud)LONG __cdecl InterlockedOr( _Inout_ LONG volatile *Destination, _In_ LONG Value );参数:
Destination [in,out]
指向第一个操作数的指针.该值将替换为操作结果.值 [in]
第二个操作数.返回值
该函数返回Destination参数的原始值.
这就是您所观察到的异常代码所必需的原因.编译器不能简单地发出OR指令,以LOCK前缀,因为该OR指令不返回以前的值.相反,它必须LOCK CMPXCHG在循环中使用奇怪的解决方法.事实上,这个看似不寻常的序列是实现互锁操作的标准模式,当底层硬件本身不支持它们时:捕获旧值,执行与新值的互锁比较和交换,并继续尝试循环,直到此尝试的旧值等于捕获的旧值.
正如您所观察到的那样,您会看到同样的事情InterlockedAnd,原因完全相同:x86 AND指令不返回原始值,因此代码生成器必须回退涉及比较和交换的一般模式,这是直接的由硬件支持.
请注意,至少在x86上InterlockedOr实现为内在函数,优化器足够聪明,可以判断您是否使用了返回值.如果是,那么它使用涉及的变通方法代码CMPXCHG.如果你忽略了返回值,那么它会继续使用LOCK OR,就像你期望的那样发出代码.
#include <intrin.h>
LONG InterlockedOrWithReturn()
{
LONG val = 42;
return _InterlockedOr(&val, 8);
}
void InterlockedOrWithoutReturn()
{
LONG val = 42;
LONG old = _InterlockedOr(&val, 8);
}
Run Code Online (Sandbox Code Playgroud)
InterlockedOrWithoutReturn, COMDAT PROC
mov DWORD PTR [rsp+8], 42
lock or DWORD PTR [rsp+8], 8
ret 0
InterlockedOrWithoutReturn ENDP
InterlockedOrWithReturn, COMDAT PROC
mov DWORD PTR [rsp+8], 42
prefetchw BYTE PTR [rsp+8]
mov eax, DWORD PTR [rsp+8]
LoopTop:
mov ecx, eax
or ecx, 8
lock cmpxchg DWORD PTR [rsp+8], ecx
jne SHORT LoopTop
ret 0
InterlockedOrWithReturn ENDP
Run Code Online (Sandbox Code Playgroud)
优化器同样适用于智能InterlockedAnd,也应该用于其他Interlocked*功能.
直觉会告诉你,LOCK OR实现比LOCK CMPXCHG循环更有效.不仅存在扩展的代码大小和循环的开销,而且还存在分支预测未命中的风险,这可能会花费大量的周期.在性能关键代码中,如果您可以避免依赖互锁操作的返回值,则可以获得性能提升.
但是,你真正应该在现代C++中使用的是std::atomic,它允许你指定所需的内存模型/语义,然后让标准库维护者处理复杂性.