visual-c++ - 在MSVC中,为什么InterlockedOr和InterlockedAnd生成循环而不是简单的锁定指令?

标签 visual-c++ lock-free data-synchronization interlocked

在 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 instructionLOCK 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/

相关文章:

c++ - 使用 Windows 函数 SendMessage 导致与 MSVC 的链接错误,但与 MinGW 的链接错误

c++ - 验证像 "-2-2"或 "--22"或 "2--2"这样的 CString 是无效的

c++ - C++11 中的无锁缓存实现

c++ - 在x86 cpu上进行比较和交流

sql - 同步客户端-服务器数据库

C 数组指针数学。按索引访问内存冲突

c++ - move : what does it take?

c++ - 从原始指针构造时 shared_ptr 是否分配?

.net - TPL DataFlow 与 BlockingCollection

c# - 使用存储过程或 C# 同步 SQL Server 2014 和 SQL Server 2014 Express 数据库