哪个运算符更快(>或> =),(<或< =)?

时间:2012-08-01 16:15:22

标签: c performance optimization assembly operators

<<=更便宜(更快),同样地>>=更便宜(更快)吗?

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

2 个答案:

答案 0 :(得分:12)

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

这并没有真正告诉我什么。所以,我们跳到基准:

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

#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个单独的运行):

testL: 337848
testLE: 338237
testG: 337888
testGE: 337787

testL: 337768
testLE: 338110
testG: 337406
testGE: 337926

testL: 338958
testLE: 338948
testG: 337705
testGE: 337829

testL: 339805
testLE: 339634
testG: 337413
testGE: 337900

testL: 340490
testLE: 339030
testG: 337298
testGE: 337593

我认为这些运算符之间的差异充其量是微不足道的,并且在现代计算世界中并没有太大的重要性。

答案 1 :(得分:3)

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

现在,对于大多数体系结构来说,由于必须刷新并重新填充管道,执行分支所需的时间比不执行分支要长。有些人做分支预测等来帮助解决这个问题。

现在有些架构的大小可能会有所不同,比较gpr0和gpr1与比较gpr0和立即数1234,可能需要更大的指令,例如,你会看到很多用x86。所以尽管这两种情况都可能是一个分支,如果少于你编码的程度,那么取决于发生什么样的寄存器来保持哪些值可以产生性能差异(确保x86做了大量的流水线操作,大量的缓存等来弥补这些问题)。另一个类似的例子是mips和or32,其中r0总是为零,它不是真正的通用寄存器,如果你写它不变,它硬连线到零,所以比较如果等于0 MIGHT花费你如果一个额外的指令或两个需要一个额外的指令来填充一个gpr,以便比较可以发生,最坏的情况是必须将一个寄存器驱逐到堆栈或内存,以释放注册以将直接放在那里,以便比较可以发生。

有些架构像arm一样有条件执行,因为你可以在每条指令的基础上执行完整的arm(非拇指)指令,所以如果你有代码

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

arm的伪代码是

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

没有实际的分支,所以没有管道问题你飞得很快,非常快。

一个架构到另一个,如果这是一个有趣的比较,如上所述,mips,或32,你必须专门执行某种比较的指令,其他像x86,msp430和绝大多数alu操作改变标志,如果你告诉它改变标志,手臂之类的东西会改变标志,否则不会如上所示。所以

while(--len)
{
  //do something
}

循环减去1也设置了标志,如果循环中的东西足够简单你可以使整个事件有条件,所以你保存在单独的比较和分支指令上,你节省了管道惩罚。 Mips通过比较解决了这个问题,分支是一条指令,它们在分支后执行一条指令,以便在管道中保存一点。

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