Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/146.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 编译器在这里做什么,允许在很少实际比较的情况下对许多值进行比较?_C++_Assembly_Optimization_X86 64_Micro Optimization - Fatal编程技术网

C++ 编译器在这里做什么,允许在很少实际比较的情况下对许多值进行比较?

C++ 编译器在这里做什么,允许在很少实际比较的情况下对许多值进行比较?,c++,assembly,optimization,x86-64,micro-optimization,C++,Assembly,Optimization,X86 64,Micro Optimization,我的问题是,在这种情况下,编译器正在做什么,以比我认为可能的更多的方式优化代码 鉴于此枚举: enum MyEnum{ 入口1, 入口2, …//Entry3..27是相同的,因大小而省略。 入口28, 入口29 }; 这个功能是: bool MyFunction(MyEnum e) { 如果( e==MyEnum::Entry1 | | e==MyEnum::Entry3 | | e==MyEnum::Entry8 | | e==MyEnum::Entry14 | | e==MyEnum::

我的问题是,在这种情况下,编译器正在做什么,以比我认为可能的更多的方式优化代码

鉴于此枚举:

enum MyEnum{
入口1,
入口2,
…//Entry3..27是相同的,因大小而省略。
入口28,
入口29
};
这个功能是:

bool MyFunction(MyEnum e)
{
如果(
e==MyEnum::Entry1 | |
e==MyEnum::Entry3 | |
e==MyEnum::Entry8 | |
e==MyEnum::Entry14 | |
e==MyEnum::Entry15||
e==MyEnum::Entry18||
e==MyEnum::Entry21 | |
e==MyEnum::Entry22||
e==MyEnum::Entry25)
{
返回true;
}
返回false;
}
对于函数,MSVC在使用-Ox优化标志()编译时生成此程序集:

当使用-O3标志编译时,Clang会生成类似的(稍微好一点,少跳一次)程序集:

MyFunction(MyEnum):                  # @MyFunction(MyEnum)
        cmp     edi, 24
        ja      .LBB0_2
        mov     eax, 20078725
        mov     ecx, edi
        shr     eax, cl
        and     al, 1
        ret
.LBB0_2:
        xor     eax, eax
        ret
这里发生了什么?我发现,即使我在函数中添加了更多的枚举比较,生成的程序集实际上并没有变得“更多”,只是这个神奇的数字(20078725)发生了变化。该数字取决于函数中正在进行的枚举比较的数量。我不明白这里发生了什么

我之所以考虑这一点,是因为我想知道,按照上述方式编写函数,或者像这样,通过按位比较编写函数是否好:

bool MyFunction2(MyEnum e)
{
如果(
e==MyEnum::Entry1 |
e==MyEnum::Entry3|
e==MyEnum::Entry8|
e==MyEnum::Entry14|
e==MyEnum::Entry15|
e==MyEnum::Entry18|
e==MyEnum::Entry21|
e==MyEnum::Entry22|
e==MyEnum::Entry25)
{
返回true;
}
返回false;
}
这将导致使用MSVC生成此程序集:

bool MyFunction2(MyEnum) PROC           ; MyFunction2
        xor     edx, edx
        mov     r9d, 1
        cmp     ecx, 24
        mov     eax, edx
        mov     r8d, edx
        sete    r8b
        cmp     ecx, 21
        sete    al
        or      r8d, eax
        mov     eax, edx
        cmp     ecx, 20
        cmove   r8d, r9d
        cmp     ecx, 17
        sete    al
        or      r8d, eax
        mov     eax, edx
        cmp     ecx, 14
        cmove   r8d, r9d
        cmp     ecx, 13
        sete    al
        or      r8d, eax
        cmp     ecx, 7
        cmove   r8d, r9d
        cmp     ecx, 2
        sete    dl
        or      r8d, edx
        test    ecx, ecx
        cmove   r8d, r9d
        test    r8d, r8d
        setne   al
        ret     0

因为我不明白第一个案例会发生什么,所以我无法真正判断哪一个在我的案例中更有效。

相当聪明!与24的第一个比较是做一个粗略的范围检查-如果它大于24或小于0,它将退出;这一点很重要,因为对幻数进行操作的后续指令对于操作数范围有一个硬上限[0,31]

对于其余部分,幻数只是一个位掩码,其位对应于设置的“良好”值

>>> bin(20078725)
'0b1001100100110000010000101'
很容易找到第一位和第三位(从1开始和从右开始计数),第8位、第14位、第15位

MSVC使用BT(位测试)指令和分支“直接”对其进行检查,而不是将其移位适当的量(以获得最低阶位置的相关位),并将其与零保持正和(避免分支)

对应于clang版本的C代码类似于:

bool MyFunction(MyEnum e) {
    if(unsigned(e) > 24) return false;
    return (20078725 >> e) & 1;
}
至于MSVC版本,它更像

inline bool bit_test(unsigned val, int bit) {
    return val & (1<<bit);
}

bool MyFunction(MyEnum e) {
    if(unsigned(e) > 24) return false;
    return bit_test(20078725, e);
}
内联布尔位测试(无符号val,整数位){

返回val&(1Ah,旧的即时位图技巧。

GCC也这样做,至少对于交换机是这样。 .不幸的是,GCC9在某些情况下有一个回归:;GCC8和更早的版本做得更好

另一个使用它的示例,这次是代码高尔夫(代码字节数最少,在本例中为x86机器代码)检测某些字母:


基本思想是使用输入作为真/假结果位图的索引。

首先,您必须进行范围检查,因为位图的宽度是固定的,x86移位会将移位计数包裹起来。我们不希望alias的高输入值进入范围,其中有些值应返回true。
cmp edi,24
/
ja
正在执行

(例如,如果最低和最高
true
值之间的范围在120到140之间,则它可能以
子edi开始,120
范围移动
cmp
之前的所有内容)

然后使用
bitmap&(1e)&1
shr
/
)检查位图中的位,该位告诉您该
e
值应返回true还是false

有许多方法可以实现该检查,逻辑上是等效的,但性能有所不同


如果范围大于32,则必须使用64位操作数大小。如果范围大于64,编译器可能根本不会尝试此优化。或者对于范围较窄的某些条件,编译器可能仍会尝试此优化

使用更大的位图(在.rodata内存中)是可能的,但可能不是大多数编译器会为您发明的。使用
bt[mem],reg
(低效)或者手动索引dword并检查是否与此代码检查即时位图的方式相同。如果您有许多高熵范围,则可能值得检查2x 64位即时位图、branchy或branchless

Clang/LLVM还有其他方法可以有效地比较多个值(当命中哪个值无关紧要时),例如,将一个值广播到SIMD寄存器并使用压缩比较。这不取决于值是否在密集范围内。()

这比我认为可能的优化了代码

这些类型的优化来自聪明的人类编译器开发人员,他们注意到源代码中的常见模式,并想出聪明的方法来实现它们。然后让编译器识别这些模式,并转换其程序逻辑的内部表示形式以使用该技巧

事实证明,
switch
和switch-like
if()
语句很常见,而积极的优化也很常见

编译器远非十全十美,但有时它们确实接近人们常说的那样;编译器会为您优化代码,这样您就可以以人类可读的方式编写代码,并且仍能以近乎最佳的方式运行。有时在小规模上也是如此


因为我不明白未来会发生什么
inline bool bit_test(unsigned val, int bit) {
    return val & (1<<bit);
}

bool MyFunction(MyEnum e) {
    if(unsigned(e) > 24) return false;
    return bit_test(20078725, e);
}