if(a<901)是否比if(a<=900)快?
与这个简单示例中的情况不同,但循环复杂代码的性能略有变化。我想这需要对生成的机器代码进行一些处理,以防它是真的。
if(a<901)是否比if(a<=900)快?
与这个简单示例中的情况不同,但循环复杂代码的性能略有变化。我想这需要对生成的机器代码进行一些处理,以防它是真的。
当前回答
我认为两者都不快。编译器在每个条件下生成具有不同值的相同机器代码。
if(a < 901)
cmpl $900, -4(%rbp)
jg .L2
if(a <=901)
cmpl $901, -4(%rbp)
jg .L3
我的示例if来自Linux上x86_64平台上的GCC。
编译器编写者是非常聪明的人,他们认为这些事情以及我们大多数人认为理所当然的其他事情。
我注意到,如果它不是常数,那么在这两种情况下都会生成相同的机器代码。
int b;
if(a < b)
cmpl -4(%rbp), %eax
jge .L2
if(a <=b)
cmpl -4(%rbp), %eax
jg .L3
其他回答
不,它在大多数架构上不会更快。您没有指定,但在x86上,所有的积分比较通常将在两个机器指令中实现:
测试或cmp指令,用于设置EFLAGS以及Jcc(跳转)指令,具体取决于比较类型(和代码布局):jne-如果不等于-->ZF=0,则跳转jz-如果零(等于)-->ZF=1则跳转jg-如果大于-->ZF=0且SF=OF,则跳转(等等)
示例(为简洁起见编辑)使用$gcc-m32-S-masm=inteltest.c编译
if (a < b) {
// Do something 1
}
编译到:
mov eax, DWORD PTR [esp+24] ; a
cmp eax, DWORD PTR [esp+28] ; b
jge .L2 ; jump if a is >= b
; Do something 1
.L2:
And
if (a <= b) {
// Do something 2
}
编译到:
mov eax, DWORD PTR [esp+24] ; a
cmp eax, DWORD PTR [esp+28] ; b
jg .L5 ; jump if a is > b
; Do something 2
.L5:
因此,两者之间的唯一区别是jg与jge指令。这两者将花费相同的时间。
我想指出的是,没有任何东西表明不同的跳转指令需要相同的时间。这个问题回答起来有点棘手,但我可以给出以下答案:在“英特尔指令集参考”中,它们都被分组在一条公共指令Jcc(如果满足条件则跳转)下。根据附录C中的《优化参考手册》进行了相同的分组。延迟和吞吐量。
Latency—执行核心,以完成形成的所有μ操作的执行指令。
吞吐量—需要的时钟周期数等待发出端口可以接受相同的指令再一次对于许多指令,指令的吞吐量可以是显著低于其延迟
Jcc的值为:
Latency Throughput
Jcc N/A 0.5
Jcc上有以下脚注:
条件跳转指令的选择应基于第3.4.1节“分支预测优化”的建议,以提高分支的可预测性。当成功预测分支时,jcc的延迟实际上为零。
因此,在英特尔文档中,对一条Jcc指令的处理方式与其他指令没有任何区别。
如果考虑用于实现指令的实际电路,可以假设EFLAGS中的不同位上有简单的“与/或”门,以确定是否满足条件。因此,没有理由让一条测试两个位的指令比只测试一个位花费更多或更少的时间(忽略比时钟周期短得多的门传播延迟)
编辑:浮点
x87浮点也是如此:(与上面的代码几乎相同,但使用double而不是int。)
fld QWORD PTR [esp+32]
fld QWORD PTR [esp+40]
fucomip st, st(1) ; Compare ST(0) and ST(1), and set CF, PF, ZF in EFLAGS
fstp st(0)
seta al ; Set al if above (CF=0 and ZF=0).
test al, al
je .L2
; Do something 1
.L2:
fld QWORD PTR [esp+32]
fld QWORD PTR [esp+40]
fucomip st, st(1) ; (same thing as above)
fstp st(0)
setae al ; Set al if above or equal (CF=0).
test al, al
je .L5
; Do something 2
.L5:
leave
ret
只有当制造计算机的人不擅长布尔逻辑的时候。他们不应该这样。
每个比较(>=<=><)都可以以相同的速度进行。
每一次比较都只是一次减法(差值),看看它是正还是负。(如果设置了msb,则数字为负数)
如何检查a>=b?Sub a-b>=0检查a-b是否为正。如何检查a<=b?Sub 0<=b-a检查b-a是否为正。如何检查a<b?Sub a-b<0检查a-b是否为负。如何检查a>b?Sub 0>b-a检查b-a是否为负。
简单地说,对于给定的操作,计算机可以在引擎盖下面执行以下操作:
a>=b==msb(a-b)==0a<=b==msb(b-a)==0a>b==msb(b-a)==1a<b==msb(a-b)==1
当然,计算机实际上也不需要执行==0或==1。对于==0,它可以将电路中的msb反相。
无论如何,他们肯定不会将a>=b计算为a>b||a==b lol
对于浮点代码,甚至在现代体系结构上,<=比较可能确实会慢一些(一条指令)。这是第一个函数:
int compare_strict(double a, double b) { return a < b; }
在PowerPC上,首先执行浮点比较(更新条件寄存器cr),然后将条件寄存器移动到GPR,将“比较小于”位移位到位,然后返回。它需要四个指令。
现在考虑一下这个函数:
int compare_loose(double a, double b) { return a <= b; }
这需要与上面的compare_strict相同的工作,但现在有两个有趣的位:“小于”和“等于”。这需要一个额外的指令(cror-condition寄存器逐位OR)将这两个位组合为一。因此,compare_sloose需要五条指令,而compare_sstrict需要四条指令。
您可能认为编译器可以这样优化第二个函数:
int compare_loose(double a, double b) { return ! (a > b); }
然而,这将错误地处理NaN。NaN1<=NaN2和NaN1>NaN2都需要评估为假。
即使有差异,你也不应该注意到。此外,在实践中,除非你要使用一些神奇的常数,否则你必须做一个额外的a+1或a-1来使条件成立,这无论如何都是一个非常糟糕的实践。
从历史上看(我们所说的是20世纪80年代和90年代初),有些架构是这样的。根本问题是整数比较本质上是通过整数减法实现的。这导致了以下情况。
Comparison Subtraction
---------- -----------
A < B --> A - B < 0
A = B --> A - B = 0
A > B --> A - B > 0
现在,当A<B时,减法必须借用高位才能正确进行减法,就像你用手进行加法和减法时一样。这个“借用”位通常被称为进位位,可以通过分支指令进行测试。如果减法等于零,则将设置第二位,称为零位,这意味着相等。
通常至少有两条条件分支指令,一条在进位位上分支,另一条在零位上分支。
现在,为了了解问题的核心,让我们扩展上一个表,以包括进位和零位结果。
Comparison Subtraction Carry Bit Zero Bit
---------- ----------- --------- --------
A < B --> A - B < 0 0 0
A = B --> A - B = 0 1 1
A > B --> A - B > 0 1 0
因此,实现a<B的分支可以在一条指令中完成,因为进位位仅在这种情况下是清除的,即,
;; Implementation of "if (A < B) goto address;"
cmp A, B ;; compare A to B
bcz address ;; Branch if Carry is Zero to the new address
但是,如果我们想进行小于或等于的比较,我们需要对零标志进行额外的检查,以捕捉相等的情况。
;; Implementation of "if (A <= B) goto address;"
cmp A, B ;; compare A to B
bcz address ;; branch if A < B
bzs address ;; also, Branch if the Zero bit is Set
因此,在某些机器上,使用“小于”比较可能会节省一条机器指令。这在亚兆赫处理器速度和1:1 CPU与内存速度比的时代是相关的,但在今天几乎完全不相关。