7

<比 便宜(快)<=,同样,>比 便宜(快>=)?

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

4

2 回答 2

13

TL;博士

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

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

于 2012-08-01T16:28:30.170 回答
3

它会有所不同,首先要检查不同的指令集以及编译器如何使用这些指令集。以 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通过比较解决了这个问题,分支是一条指令,它们在分支之后执行一条指令以节省一点管道。

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

于 2012-08-01T18:03:14.597 回答