哪个运算符更快(> 或 >=)、(< 或 <=)? [关闭]

Posted

技术标签:

【中文标题】哪个运算符更快(> 或 >=)、(< 或 <=)? [关闭]【英文标题】:Which operator is faster (> or >=), (< or <=)? [closed] 【发布时间】:2012-07-30 14:15:43 【问题描述】:

&lt; 是否比&lt;= 便宜(更快),同样&gt; 是否比&gt;= 便宜(更快)?

免责声明:我知道我可以测量,但这只会在我的机器上,我不确定答案是否可能是“特定于实施”或类似的东西。

【问题讨论】:

我猜他们在大多数架构中都编译成一条指令,但答案是:谁在乎? 就生成的汇编指令而言,它们几乎是等价的,如果这就是您的要求的话。 我理解你的问题背后的精神,但是:你问这个是出于学术兴趣,还是因为你认为这可能会影响你的应用程序的性能?它不会。如果存在差异,您的应用程序中的其他因素绝对会淹没。不是 2 倍或 10 倍,而是 1m 或更多。我敢打赌你根本无法测量它。 我认为这个问题是主题性和建设性的。虽然它可能不适用于所有架构,但它表明可以提供一些基准来回答这个问题。 仅仅考虑这个问题,您花费的时间比答案所能节省的时间还多;-) 【参考方案1】:

TL;DR

这四个操作员之间似乎几乎没有区别,因为对我来说,它们的执行时间几乎相同(在不同的系统上可能会有所不同!)。因此,当有疑问时,只需使用对情况最有意义的运算符(尤其是在使用 C++ 时)。

所以,事不宜迟,这里是长解释:

假设整数比较:

就生成的程序集而言,结果取决于平台。在我的电脑上(Apple LLVM Compiler 4.0, x86_64),结果(生成的程序集如下):

a < b (uses 'setl'):

movl    $10, -8(%rbp)
movl    $15, -12(%rbp)
movl    -8(%rbp), %eax
cmpl    -12(%rbp), %eax
setl    %cl
andb    $1, %cl
movzbl  %cl, %eax
popq    %rbp
ret

a <= b (uses 'setle'):

movl    $10, -8(%rbp)
movl    $15, -12(%rbp)
movl    -8(%rbp), %eax
cmpl    -12(%rbp), %eax
setle   %cl
andb    $1, %cl
movzbl  %cl, %eax
popq    %rbp
ret

a > b (uses 'setg'):

movl    $10, -8(%rbp)
movl    $15, -12(%rbp)
movl    -8(%rbp), %eax
cmpl    -12(%rbp), %eax
setg    %cl
andb    $1, %cl
movzbl  %cl, %eax
popq    %rbp
ret

a >= b (uses 'setge'): 

movl    $10, -8(%rbp)
movl    $15, -12(%rbp)
movl    -8(%rbp), %eax
cmpl    -12(%rbp), %eax
setge   %cl
andb    $1, %cl
movzbl  %cl, %eax
popq    %rbp
ret

这并不能告诉我太多。所以,我们跳到一个基准:

女士们先生们,结果出来了,我创建了以下测试程序(我知道“时钟”不是计算这样的结果的最佳方法,但现在必须这样做)。

#include <time.h>
#include <stdio.h>

#define ITERS 100000000

int v = 0;

void testL()

    clock_t start = clock();
    
    v = 0;
    
    for (int i = 0; i < ITERS; i++) 
        v = i < v;
    
    
    printf("%s: %lu\n", __FUNCTION__, clock() - start);


void testLE()

    clock_t start = clock();
    
    v = 0;
    
    for (int i = 0; i < ITERS; i++)
    
        v = i <= v;
    
    
    printf("%s: %lu\n", __FUNCTION__, clock() - start);


void testG()

    clock_t start = clock();
    
    v = 0;
    
    for (int i = 0; i < ITERS; i++) 
        v = i > v;
    
    
    printf("%s: %lu\n", __FUNCTION__, clock() - start);


void testGE()

    clock_t start = clock();
    
    v = 0;
    
    for (int i = 0; i < ITERS; i++) 
        v = i >= v;
    
    
    printf("%s: %lu\n", __FUNCTION__, clock() - start);


int main()

    testL();
    testLE();
    testG();
    testGE();

在我的机器上(使用 -O0 编译),给了我这个(5 次单独运行):

测试号:337848 测试LE:338237 测试G:337888 测试GE:337787 测试号:337768 测试LE:338110 测试G:337406 测试GE:337926 测试号:338958 测试LE:338948 测试G:337705 测试GE:337829 测试号:339805 测试LE:339634 测试G:337413 测试GE:337900 测试L:340490 测试LE:339030 测试G:337298 测试GE:337593

我认为这些运算符之间的差异充其量是很小的,在现代计算世界中并没有太大的影响。

【讨论】:

汇编代码实际上说明了很多。这说明所有这些 sn-ps 必须花费完全相同的时间,所有情况都相同。无论cc 是什么,setcc 都需要 1 个周期(P4 除外,它需要 3 个周期)。但这有什么关系呢?比较运算符几乎从不以这种方式使用 - 比较jcc 的性能(无论cc 也一样)似乎更合乎逻辑。 我第二个@harold。该程序集说明了很多 - 所有比较都是使用相同的 cmpl 指令完成的,该指令完成了比较其参数的繁重工作。基本上它从第一个参数中减去第二个参数(丢弃结果),然后 ALU 设置标志寄存器中的相应位。然后可以对它们进行测试、分支或用于设置内存/寄存器值。 @harold 他的意思可能是“它并没有告诉 太多” @drhirsch 是的,这就是我的意思,更新了答案以反映这一点。【参考方案2】:

它各不相同,首先要检查不同的指令集以及编译器如何使用这些指令集。以 openrisc 32 为例,它显然受到 mips 的启发,但条件不同。对于 or32 有比较和设置标志指令,比较这两个寄存器如果小于或等于无符号则设置标志,比较这两个寄存器如果相等则设置标志。然后有两个条件分支指令在标志设置时分支和在标志清除时分支。编译器必须遵循这些路径之一,但小于、小于、小于或等于、大于等都将使用相同数量的指令,条件分支的执行时间相同,不执行的执行时间相同条件分支。

现在对于大多数架构来说,执行分支肯定比不执行分支花费更长的时间,因为必须刷新和重新填充管道。有些人做分支预测等来帮助解决这个问题。

现在某些体系结构的指令大小可能会有所不同,比较 gpr0 和 gpr1 与比较 gpr0 和立即数 1234,可能需要更大的指令,例如在 x86 中你会看到很多。因此,尽管这两种情况都可能是一个分支,如果少于您的编码方式,则取决于哪些寄存器碰巧保存了哪些值可以产生性能差异(确保 x86 做了很多流水线、大量缓存等来弥补这些问题)。另一个类似的例子是 mips 和 or32,其中 r0 始终为零,它不是真正的通用寄存器,如果你写它它不会改变,它被硬连线为零,所以如果等于 0 比较可能会花费你如果等于某个其他数字,则不仅仅是比较,如果需要一两条额外的指令来用该立即数填充 gpr,以便可以进行比较,最坏的情况是必须将寄存器驱​​逐到堆栈或内存中,以释放注册以将立即数放入其中以便进行比较。

有些架构像 arm 一样有条件执行,对于完整的 arm(不是 thumb)指令,您可以在每条指令的基础上执行,所以如果您有代码

if(i==7) j=5; else j=9;

arm 的伪代码是

cmp i,#7
moveq j,#5
movne j,#7

没有实际的分支,所以没有管道问题,您可以飞速通过,非常快。

如果这是一个有趣的比较的话,一个架构与另一个架构,如提到的 mips 或 32,您必须专门执行某种指令进行比较,其他如 x86、msp430 和绝大多数每个 alu 操作都会更改标志,如果您告诉它更改标志,arm 等会更改标志,否则不会如上所示。所以一个

while(--len)

  //do something

循环减 1 也设置标志,如果循环中的内容足够简单,您可以使整个事情有条件,因此您节省了单独的比较和分支指令,并节省了管道惩罚。 Mips通过比较解决了这个问题,分支是一条指令,它们在分支之后执行一条指令以节省一点管道。

一般的答案是您不会看到差异,各种条件的指令数量、执行时间等都是相同的。小立即数与大立即数等特殊情况可能会对极端情况产生影响,或者编译器可能会根据您进行的比较而简单地选择以不同的方式进行操作。如果您尝试重新编写算法以使其给出相同的答案,但使用小于而不是大于和等于,则您可能会更改代码以获得不同的指令流。同样,如果您执行的性能测试过于简单,编译器可以/将优化比较完成并仅生成结果,这可能会因您的测试代码而异,从而导致不同的执行。所有这一切的关键是反汇编你想要比较的东西,看看指令有什么不同。这将告诉您是否应该看到任何执行差异。

【讨论】:

以上是关于哪个运算符更快(> 或 >=)、(< 或 <=)? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

哪个运算符更快:?: 或 &&

哪个演员更快 static_cast<int> () 或 int()

对于操作计数()。 std::set<void*> 或 std::unordered_set<void*> 哪个更快?

哪个更快?组合或多个查询?

TOP 1 或 EXISTS - 哪个更快?

哪个更快:清除集合或实例化新的