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
其他回答
至少,如果这是真的,编译器可以轻松地优化a<=b到!(a>b),因此,即使比较本身实际上较慢,但除了最简单的编译器之外,您也不会注意到差异。
假设我们讨论的是内部整数类型,不可能一个比另一个更快。它们显然在语义上是相同的。它们都要求编译器做完全相同的事情。只有一个严重损坏的编译器才能为其中一个生成劣质代码。
如果在某些平台上,对于简单整数类型,<比<=快,编译器应始终将常量的<=转换为<。任何没有这样做的编译器都将是一个糟糕的编译器(对于该平台)。
TL;DR答案
对于架构、编译器和语言的大多数组合,<不会比<=快。
完整答案
其他答案都集中在x86架构上,我不太了解ARM架构(您的示例汇编程序似乎是这样),无法对生成的代码进行具体评论,但这是一个非常特定于架构的微优化示例,很可能是反优化,也可能是优化。
因此,我认为这种微优化是货物崇拜编程的一个例子,而不是最佳软件工程实践。
反例
可能有一些架构是优化的,但我知道至少有一种架构可能是相反的。古老的Transputer体系结构只有等于和大于或等于的机器代码指令,因此所有比较都必须从这些原语构建。
即使如此,在几乎所有的情况下,编译器都可以以这样的方式对求值指令进行排序,即在实践中,没有任何比较比任何其他比较都有任何优势。但最坏的情况是,它可能需要添加反向指令(REV)来交换操作数堆栈上的前两项。这是一个单字节指令,它需要一个周期才能运行,因此开销最小。
总结
像这样的微优化是优化还是反优化取决于您正在使用的特定架构,因此养成使用特定架构的微优化的习惯通常是一个坏主意,否则您可能会在不合适时本能地使用微优化,看起来这正是您正在阅读的书所倡导的。
不,它在大多数架构上不会更快。您没有指定,但在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
这将高度依赖于C编译到的底层架构。某些处理器和架构可能具有等于或小于等于的显式指令,这些指令以不同的周期执行。
但这很不寻常,因为编译器可以绕过它,使它变得无关紧要。