assembly 哪个运算符更快(>或>=),(〈或〈=)?[closed]

bakd9h0s  于 2022-11-13  发布在  其他
关注(0)|答案(2)|浏览(116)

就目前而言,此问题不适合我们的问答形式。我们希望答案能得到事实、参考资料或专业知识的支持,但此问题可能会引发辩论、争论、投票或更长时间的讨论。如果您认为此问题可以改进并可能重新讨论,请visit the help center寻求指导。
10年前就关门了。
<是否比<=更便宜(更快)?同样,>是否比>=更便宜(更快)?
免责声明:我知道我可以测量,但这将只在我的机器上,我不确定答案是否可以是“特定于实现”或类似的东西。

j5fpnvbx

j5fpnvbx1#

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次单独运行):

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

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

syqv5f0l

syqv5f0l2#

它有很多种,首先从检查不同的指令集以及编译器如何使用这些指令集开始。以openrisc 32为例,它显然是受mips启发的,但对条件的处理方式不同。对于or 32,有比较和设置标志指令,比较这两个寄存器,如果小于或等于无符号,则设置标志,比较这两个寄存器,如果相等,则设置标志。然后有两个条件分支指令,标志设置时转移和标志清除时转移。编译器必须遵循这些路径之一,但小于、小于或等于、大于等等都将使用相同数量的指令、用于条件分支的相同执行时间以及用于不进行条件分支的相同执行时间。
对于大多数体系结构来说,执行分支比不执行分支花费更长的时间,因为必须刷新和重新填充管道。有些体系结构会进行分支预测等来帮助解决这个问题。
现在,在一些架构中,指令的大小可能不同,比较gpr 0和gpr 1与比较gpr 0和立即数1234,可能需要较大的指令,例如,您将在x86中经常看到这一点。因此,尽管这两种情况都可能是分支,如果小于,则根据哪些寄存器碰巧保存哪些值来编码,可以产生性能差异(当然x86做了很多流水线操作,很多缓存等来弥补这些问题)另一个类似的例子是mips和or 32,其中r 0总是零,它不是真正的通用寄存器,如果你写它不会改变,它被硬连接到零,因此,如果比较值等于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
}

如果循环中的内容足够简单,你可以把整个过程都设为条件循环,这样就保存了单独的比较和分支指令,也节省了流水线开销。Mips解决了这个问题,因为比较和分支是一条指令,它们在分支之后执行一条指令,从而节省了流水线开销。
一般的答案是,你不会看到有什么区别,指令的数量,执行时间等对于各种条件句都是一样的。特殊情况如小立即数vs大立即数等可能对角落情况有影响,或者编译器可能只是根据您所做的比较而选择完全不同的方式来执行。如果您尝试重新写你的算法,让它给予相同的答案,但使用小于,而不是大于和等于,你可能会改变代码足以得到一个不同的指令流.同样,如果你执行太简单的性能测试,编译器可以/将优化出比较完成并仅生成结果,这可能会因您的测试代码而异,从而导致不同的执行。所有这一切的关键是反汇编您想要比较的东西,并查看指令有何不同。这将告诉您是否应该看到任何执行差异。

相关问题