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 == Value为Value == NewValue(简单的长值) - 生成的代码严重更改!
我尝试使用两个版本CL- 最新版本19.00.24210.0和14.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]?
- 为什么 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++ 标准的替代品。
相应的标准函数直接返回布尔值,并间接返回观察值:
atomic_compare_exchange_strong,返回_Bool,并且观察到的值由您传递预期的指针返回std::atomic<T>::compare_exchange_strong,返回bool,观察值是通过引用返回的,而不是预期的不过,这些标准替代品可能已经存在_InterlockedCompareExchange或 __sync_bool_compare_and_swap在幕后。
关于优化:
<stdatomic.h>C 头文件| 归档时间: |
|
| 查看次数: |
197 次 |
| 最近记录: |