在 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,然后让标准库维护者处理复杂性。