_InterlockedCompareExchange 优化

RbM*_*bMm 5 c optimization x86 assembly x86-64

看看这段代码

extern "C" long _InterlockedCompareExchange(long volatile * _Destination, long _Exchange, long _Comparand);

#define MAGIC 1

// Unlike InterlockedIncrement this function not increment from 0 to 1, but return FALSE

bool TryLock(long* pLock)
{
    long Value = *pLock, NewValue;

    for ( ; Value; Value = NewValue)
    {
        NewValue = _InterlockedCompareExchange(pLock, Value + 1, Value);

        if (
#if MAGIC
            NewValue == Value
#else
            Value == NewValue
#endif
            ) return true;
    }

    return false;
}
Run Code Online (Sandbox Code Playgroud)

如果设置有#define MAGIC 0什么改变吗?按想法一定不是。但是如果使用CL.EXE64 位编译器,如果我们更改NewValue == ValueValue == NewValue(简单的长值) - 生成的代码严重更改!

我尝试使用两个版本CL- 最新版本19.00.24210.014.00.50727.762(超过 10 年 - 2006 年 12 月)我在所有测试中都获得了绝对相同的代码。用标志编译cl /c /FA /O1- 所以/O1优化(与 相同的结果/Oxs

MAGIC 1( NewValue == Value)

TryLock PROC
    mov eax, [pLock]
    jmp @@
@@loop:
    lea edx, [rax+1]
    lock cmpxchg [pLock], edx
    je  @@exit
@@:
    test    eax, eax
    jne @@loop
    ret
@@exit:
    mov al, 1
    ret
TryLock ENDP
Run Code Online (Sandbox Code Playgroud)

但与MAGIC 0( Value == NewValue)

TryLock PROC
    mov r8d, [pLock]
    test    r8d, r8d
    je  @@0
@@loop:
    lea edx, [r8+1]
    mov eax, r8d
    lock cmpxchg [pLock], edx
    cmp r8d, eax        ; !!!!!!!!
    je  @@exit
    test    eax, eax
    mov r8d, eax
    jne @@loop
@@0:
    xor al, al
    ret
@@exit:
    mov al, 1
    ret
TryLock ENDP
Run Code Online (Sandbox Code Playgroud)

代码变大,但主要在指令上有显着差异

cmp Value, NewValue

之后lock cmpxchg在第二个变体。真的lock cmpxchg [p], NewValue你自己设置或重置ZF标志并额外cmp Value, NewValue变得多余。如果我们用汇编编写,我们可以省略它,但是c/c++我们无法使用ZF条件分支。没有像ifzf { /* if ZF == 1 */ } else { /* if ZF == 0 */ }结果这样的语句我们需要写if (NewValue == Value) {} else {} ,结果必须cmp NewValue, Value在生成的程序集中。但是我是如何发现CL x64(但不是x86!)已经超过 10 年了(想想所有版本)接下来做

这段代码

NewValue = _InterlockedCompareExchange(p, fn(OldValue), OldValue);
if (OldValue == NewValue) ...
Run Code Online (Sandbox Code Playgroud)

转换成

mov eax, OldValue
lock cmpxchg [p], fn(OldValue)
mov NewValue, eax
cmp OldValue, eax ; !!!!
jne @@
....
Run Code Online (Sandbox Code Playgroud)

但是这个代码

NewValue = _InterlockedCompareExchange(p, fn(OldValue), OldValue);
if (NewValue == OldValue) ...
Run Code Online (Sandbox Code Playgroud)

转换成

mov eax, OldValue
lock cmpxchg [p], fn(OldValue)
mov NewValue, eax
jne @@
...
Run Code Online (Sandbox Code Playgroud)

所以CL理解cmpxchg语义并可以进行优化,但仅限于某些情况。

我在几个测试函数中测试了这个特性,到处都得到了相同的结果(非常旧的和新的CL

extern "C" long _InterlockedCompareExchange(long volatile * _Destination, long _Exchange, long _Comparand);

typedef long (*FN)(long* pLock, long Value);

#define MAGIC 1

void TestZF1(long* pLock)
{
    long Value = *pLock, NewValue;

    do 
    {
        Value++;
        NewValue = _InterlockedCompareExchange(pLock, Value ^ 1, Value);
    } while (
#if MAGIC
        NewValue != Value
#else
        Value != NewValue
#endif
        );
}

long TestZF2(long* pLock, FN fn1, FN fn2)
{
    long Value = *pLock, NewValue;

    NewValue = _InterlockedCompareExchange(pLock, Value ^ 1, Value);

    return (
#if MAGIC
        NewValue == Value
#else
        Value == NewValue
#endif
        ? fn1 : fn2) (pLock, NewValue);
}
Run Code Online (Sandbox Code Playgroud)

和生成的程序集:

TestZF1 PROC
    mov r8d, DWORD PTR [rcx]
@@loop:
    add r8d, 1
    mov edx, r8d
    mov eax, r8d
    xor edx, 1
    lock cmpxchg [rcx], edx
IF !MAGIC
    cmp r8d,eax     ; ! in TestZF1 different exactly in this instruction
ENDIF
    jne @@loop
    ret 0
TestZF1 ENDP

IF MAGIC

TestZF2 PROC
    mov r9d, [rcx]
    mov eax, [rcx]
    xor r9d, 1
    lock cmpxchg [rcx], r9d
    cmove   r8, rdx
    mov edx, eax
    jmp r8
TestZF2 ENDP

ELSE

TestZF2 PROC
    mov r10d, [rcx]
    mov r9d, r10d
    xor r9d, 1
    mov eax, r10d
    lock cmpxchg [rcx], r9d
    cmp r10d, eax   ; !!!!!!!!
    cmove   r8, rdx     
    mov edx, eax
    jmp r8
TestZF2 ENDP

ENDIF
Run Code Online (Sandbox Code Playgroud)

几个问题:

  • 为什么CL x64优化案例if (NewValue == Value)而不是优化if (Value == NewValue)
  • 这是有意识的,专门设计的,还是突然而未知的?
  • 为什么CL x86不做这个优化?我所有的测试cmp Value,NewValue指令中的最低限度是多少
  • 是否可以在c/c++没有汇编程序的情况下编写代码,以便在 x86 上使用CL
  • 有趣 - 其他c/c++编译器是否有这种优化_InterlockedCompareExchange[Pointer]

Ale*_*iev 3

  • 为什么 CL x64 优化 case if (NewValue == Value) 但不优化 if (Value == NewValue) ?
  • 这是有意识的、特别设计的,还是突然的、未知的?

我确信这是一个错误,所以我已经报告了它

如果他们做出回应,我们就会知道这是否是一个错误。

  • 为什么CL x86不做这个优化?我所有测试中 cmp Value,NewValue 指令的最小值是多少

x86 性能可能没有优化到与 x86-64 相同的水平,因为它是次要的。尽管可能会被报告为另一个错过的优化错误。

  • 是否可以在 c/c++ 上编写代码,无需汇编器,以便使用 CL 在 x86 上实现此功能?

显然不是。但clang-cl它随 Visual Studio 2019 一起提供,并且应该非常接近地模拟 CL,似乎做得更好。它也受到 的影响MAGIC,但是当启用它时,它会在 x86 上生成更好的代码。

  • 有趣 - 其他 c/c++ 编译器是否对 _InterlockedCompareExchange[Pointer] 有这种优化?

其他编译器有单独的__sync_bool_compare_and_swap__sync_val_compare_and_swap,他们为bool版本https://godbolt.org/z/j97aEG5GY实现了这种优化


请注意,_InterlockedCompareExchange以及__sync_bool_compare_and_swap都是非标准的,并且有 C 标准和 C++ 标准的替代品。

相应的标准函数直接返回布尔值,并间接返回观察值:

不过,这些标准替代品可能已经存在_InterlockedCompareExchange__sync_bool_compare_and_swap在幕后。

关于优化: