按位运算符,而不是vs xor用于分支

时间:2014-03-13 01:45:24

标签: c++ branch bitwise-operators

在询问this SO question之后,我收到了来自@ AndonM.Coleman的非常有趣的评论,我必须验证。

  

由于您的反汇编代码是为x86编写的,因此值得指出XOR将设置/清除零标志,而NOT则不会(如果您想要执行按位操作而不影响依赖于标志的跳转条件,则有时非常有用以前的操作)。现在,考虑到你没有直接编写汇编,你真的无法以有意义的方式访问这个标志,所以我怀疑这是赞成一个而不是另一个的原因。

他的评论让我感到好奇,如果下面的代码会产生相同的汇编指令

#include <iostream>

int main()
{
    unsigned int val = 0;

    std::cout << "Enter a numeric value: ";
    std::cin >> val;

    if ( (val ^ ~0U) == 0)
    {
        std::cout << "Value inverted is zero" << std::endl;
    } else
    {
        std::cout << "Value inverted is not zero" << std::endl;
    }

    if ( (~val) == 0)
    {
        std::cout << "Value inverted is zero" << std::endl;
    } else
    {
        std::cout << "Value inverted is not zero" << std::endl;
    }

    return 0;
}

对于以下两个操作

if ( (val ^ ~0U) == 0 )

if ( (~val) == 0 )

Visual Studio 2010中的未优化版本提供了以下反汇编:

    if ( (val ^ ~0U) == 0)
00AD1501  mov         eax,dword ptr [val]  
00AD1504  xor         eax,0FFFFFFFFh  
00AD1507  jne         main+86h (0AD1536h)  


    if ( (~val) == 0)
00AD1561  mov         eax,dword ptr [val]  
00AD1564  not         eax  
00AD1566  test        eax,eax  
00AD1568  jne         main+0E7h (0AD1597h)  

我的问题是优化问题。写

是否更好
if ( (val ^ ~0U) == 0)

if ( (~val) == 0)

1 个答案:

答案 0 :(得分:3)

这取决于很多事情,但主要是你告诉编译器优化的(如果有的话)。

如果编译器设置为针对大小进行优化(最小字节码),那么有时它会在看似奇怪的地方使用XOR。例如,可变长度编码方案X86使用可以通过XOR将寄存器设置为 0 ,其代码字节数少于使用MOV指令所需的代码字节数。

考虑使用XOR

的代码
if ( (val ^ ~0U) == 0 )  /* 3-bytes to negate and test (x86) */

XOR eax,0FFFFFFFFh需要3字节 AND 设置/清除零标志(ZF)

现在,请考虑使用NOT的代码:

if ( (~val) == 0)        /* 4-bytes to negate and test (x86) */

NOT eax被编码为2字节指令,但不会影响CPU标志。

TEST eax,eax增加了2个字节,是设置/清除零标志(ZF)所必需的

NOT也是一个简单的指令,但由于它不会影响任何CPU标志,因此您必须在发出TEST指令之后将其用于分支,如代码中所示。这实际上产生了更大的字节码,因此设置为优化大小的智能编译器可能试图避免使用NOT。这些指令一起完成多少个周期在CPU生成之间有所不同,而智能编译器也会在告知优化速度时将其考虑在决策中。

<小时/> 如果你不是编写手工调整的程序集,最好使用人类最清楚的东西,并希望编译器足够聪明,可以选择不同的指令/调度/等。根据编译时的要求优化大小/速度。编译器有一套智能的启发式方法,用于选择和安排指令,他们比普通编码器更了解目标CPU架构。

如果你后来发现这个分支确实是一个瓶颈而且没有更高层次的解决问题的方法,那么你可以进行一些低级调整。然而,除非您的目标是低功耗嵌入式CPU或内存受限设备,否则这些日子的重点是如此微不足道。我通过手工调整来获得足够性能的唯一地方就是算法,这些算法受益于数据并行性,而且编译器不够智能,无法有效地利用像MMX / SSE这样的专用指令集。