在 x64 的 MSVC (19.10.25019) 上,
InterlockedOr(&g, 1)
生成此代码序列:
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
我本来期望更简单(并且无循环):
mov eax, 1
lock or [?g@@3JC], eax
InterlockedAnd
生成与 InterlockedOr
类似的代码。
这条指令必须有一个循环似乎效率极低。为什么会生成此代码?
(顺便说一句:我使用 InterlockedOr 的全部原因是对变量进行原子加载 - 我后来了解到 InterlockedCompareExchange 是实现变量的方法这样做。对我来说很奇怪的是没有 InterlockedLoad(&x)
,但我离题了...)
最佳答案
InterlockedOr
的书面契约(Contract)它是否返回原始值:
InterlockedOr
Performs an atomic OR operation on the specified
LONG
values. The function prevents more than one thread from using the same variable simultaneously.LONG __cdecl InterlockedOr( _Inout_ LONG volatile *Destination, _In_ LONG Value );
Parameters:
Destination [in, out]
A pointer to the first operand. This value will be replaced with the result of the operation.Value [in]
The second operand.Return value
The function returns the original value of the Destination parameter.
这就是为什么需要您观察到的不寻常代码。编译器不能简单地发出 OR
instruction与 LOCK
prefix ,因为 OR
指令不返回先前的值。相反,它必须使用奇怪的解决方法 LOCK CMPXCHG
循环中。事实上,当底层硬件本身不支持互锁操作时,这种明显不寻常的序列是实现互锁操作的标准模式:捕获旧值,与新值执行互锁比较和交换,并继续尝试循环直到本次尝试的旧值等于捕获的旧值。
正如您所观察到的,您会在 InterlockedAnd
中看到同样的情况。 ,出于完全相同的原因:x86 AND
instruction不返回原始值,因此代码生成器必须回退到涉及比较和交换的一般模式,这是硬件直接支持的。
请注意,至少在 InterlockedOr
作为内在函数实现的 x86 上,优化器足够智能,可以判断您是否正在使用返回值。如果是,那么它会使用涉及 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);
}
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
优化器对于 InterlockedAnd 来说同样智能,并且应该适用于 the other Interlocked*
functions ,还有。
直觉告诉您,循环中的LOCK OR
实现比LOCK CMPXCHG
更有效。不仅会增加代码大小和循环开销,而且还会面临分支预测失败的风险,这可能会花费大量周期。在性能关键型代码中,如果可以避免依赖互锁操作的返回值,则可以获得性能提升。
但是,您在现代 C++ 中真正应该使用的是 std::atomic
,它允许您指定所需的内存模型/语义,然后让标准库维护人员处理复杂性。
关于visual-c++ - 在MSVC中,为什么InterlockedOr和InterlockedAnd生成循环而不是简单的锁定指令?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47019366/