在 MSVC 中,为什么 InterlockedOr 和 InterlockedAnd 生成循环而不是简单的锁定指令?
In MSVC, why do InterlockedOr and InterlockedAnd generate a loop instead of a simple locked instruction?
在 MSVC for x64 (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
的记录合同具有 return 原始值:
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 with a LOCK
prefix, because the OR
instruction does not return the previous value. Instead, it has to use the odd workaround with LOCK CMPXCHG
。事实上,这种明显不寻常的序列是在底层硬件本身不支持时实现互锁操作的标准模式:捕获旧值,执行与新值的互锁比较和交换,然后继续尝试循环直到这次尝试的旧值等于捕获的旧值。
如您所见,您看到 InterlockedAnd
, for exactly the same reason: the x86 AND
instruction 与 return 原始值相同,因此代码生成器必须回退到涉及比较和交换的一般模式,硬件直接支持。
请注意,至少在 InterlockedOr
作为内部实现的 x86 上,优化器足够智能,可以判断您是否使用 return 值。如果是,那么它会使用涉及 CMPXCHG
的解决方法代码。如果您忽略 return 值,那么它将继续并使用 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
更有效。不仅有扩展的代码大小和循环的开销,而且您还冒着分支预测未命中的风险,这会花费大量的周期。在性能关键代码中,如果可以避免依赖 return 值进行互锁操作,则可以获得性能提升。
然而,你 真正 在现代 C++ 中应该使用的是 std::atomic
,它允许你指定所需的内存 model/semantics,然后让标准库维护者处理复杂性。
在 MSVC for x64 (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
的记录合同具有 return 原始值:
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 with a LOCK
prefix, because the OR
instruction does not return the previous value. Instead, it has to use the odd workaround with LOCK CMPXCHG
。事实上,这种明显不寻常的序列是在底层硬件本身不支持时实现互锁操作的标准模式:捕获旧值,执行与新值的互锁比较和交换,然后继续尝试循环直到这次尝试的旧值等于捕获的旧值。
如您所见,您看到 InterlockedAnd
, for exactly the same reason: the x86 AND
instruction 与 return 原始值相同,因此代码生成器必须回退到涉及比较和交换的一般模式,硬件直接支持。
请注意,至少在 InterlockedOr
作为内部实现的 x86 上,优化器足够智能,可以判断您是否使用 return 值。如果是,那么它会使用涉及 CMPXCHG
的解决方法代码。如果您忽略 return 值,那么它将继续并使用 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
更有效。不仅有扩展的代码大小和循环的开销,而且您还冒着分支预测未命中的风险,这会花费大量的周期。在性能关键代码中,如果可以避免依赖 return 值进行互锁操作,则可以获得性能提升。
然而,你 真正 在现代 C++ 中应该使用的是 std::atomic
,它允许你指定所需的内存 model/semantics,然后让标准库维护者处理复杂性。