是<快于<=?
我正在读一本书,作者说if( a < 901 )
比if( a <= 900 )
快。
与这个简单的例子不完全一样,但是在循环复杂代码上有轻微的性能变化。 我想这必须用生成的机器代码来做,以防万一。
不,在大多数架构上它不会更快。 您没有指定,但在x86上,所有整数比较将通常在两个机器指令中实现:
test
或cmp
指令,设置EFLAGS
Jcc
(跳转)指令,取决于比较类型(和代码布局): jne
- 如果不相等则跳转 - > ZF = 0
jz
- 如果零(等于) - > ZF = 1
跳转 jg
- 如果更大则跳转 - > ZF = 0 and SF = OF
示例 (为简洁起见)编译时使用$ gcc -m32 -S -masm=intel test.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:
和
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“延迟时间和吞吐量”下进行相同的分组。
延迟 - 执行内核完成执行构成指令的所有μops所需的时钟周期数。
吞吐量 - 在发布端口可以再次自由接受相同指令之前等待所需的时钟周期数。 对于许多指令而言,指令的吞吐量可能远远低于其延迟
Jcc
的值是:
Latency Throughput
Jcc N/A 0.5
在Jcc
上有以下脚注:
7)条件跳转指令的选择应基于第3.4.1节“分支预测优化”的建议,以提高分支的可预测性。 当分支预测成功时, jcc
的延迟实际上为零。
因此,英特尔文档中的任何内容都不会与其他Jcc
指令有所不同。
如果考虑用于实现指令的实际电路,可以假设在EFLAGS
的不同位上会有简单的AND / OR门,以确定条件是否满足。 那么没有理由说测试两位的指令所花的时间或多或少比只测试一个指令的时间少(忽略门传播延迟,这比时钟周期要小得多)。
编辑:浮点
这也适用于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
从历史上看(我们正在谈论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
因此,在某些机器上,使用“少于”比较可能会节省一条机器指令。 这与亚MHz速度和1:1 CPU到内存速度比的时代相关,但现在几乎完全不相关。
假设我们正在谈论内部整数类型,没有办法可以比另一个更快。 它们在语义上显然是相同的。 他们都要求编译器做同样的事情。 只有一个可怕的破碎的编译器会为其中的一个产生较差的代码。
如果有一些平台的<
比<=
简单的整数类型更快,那么编译器总是应该将<=
转换为<
常量。 任何编译器不会只是一个糟糕的编译器(针对该平台)。
上一篇: Is < faster than <=?
下一篇: Why is reading lines from stdin much slower in C++ than Python?