x > -1 vs x >= 0, is there a performance difference
我听过一位老师把这个丢了一次,从那以后它就一直在烦我。假设我们要检查整数
1 2 3 | if (x > -1){ //do stuff } |
和
1 2 3 | if (x >= 0){ //do stuff } |
据这位老师说,
在现实世界中没有任何区别。
让我们来看一下各种编译器为各种目标生成的一些代码。
- 我假设一个有符号的int操作(这似乎是操作的意图)
- 我的调查局限于C和我手头上的编译器(不可否认是一个很小的样本——GCC、MSVC和IAR)。
- 已启用基本优化(gcc为
-O2 ,msvc为/Ox ,iar为-Oh ) 使用以下模块:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21void my_puts(char const* s);
void cmp_gt(int x)
{
if (x > -1) {
my_puts("non-negative");
}
else {
my_puts("negative");
}
}
void cmp_gte(int x)
{
if (x >= 0) {
my_puts("non-negative");
}
else {
my_puts("negative");
}
}
下面是它们各自为比较操作产生的结果:
MSVC 11目标臂:
1 2 3 4 5 6 7 8 9 10 | // if (x > -1) {... 00000 |cmp_gt| PROC 00000 f1b0 3fff cmp r0,#0xFFFFFFFF 00004 dd05 ble |$LN2@cmp_gt| // if (x >= 0) {... 00024 |cmp_gte| PROC 00024 2800 cmp r0,#0 00026 db05 blt |$LN2@cmp_gte| |
MSVC 11以x64为目标:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // if (x > -1) {... cmp_gt PROC 00000 83 f9 ff cmp ecx, -1 00003 48 8d 0d 00 00 // speculative load of argument to my_puts() 00 00 lea rcx, OFFSET FLAT:$SG1359 0000a 7f 07 jg SHORT $LN5@cmp_gt // if (x >= 0) {... cmp_gte PROC 00000 85 c9 test ecx, ecx 00002 48 8d 0d 00 00 // speculative load of argument to my_puts() 00 00 lea rcx, OFFSET FLAT:$SG1367 00009 79 07 jns SHORT $LN5@cmp_gte |
MSVC 11针对x86:
1 2 3 4 5 6 7 8 9 10 | // if (x > -1) {... _cmp_gt PROC 00000 83 7c 24 04 ff cmp DWORD PTR _x$[esp-4], -1 00005 7e 0d jle SHORT $LN2@cmp_gt // if (x >= 0) {... _cmp_gte PROC 00000 83 7c 24 04 00 cmp DWORD PTR _x$[esp-4], 0 00005 7c 0d jl SHORT $LN2@cmp_gte |
GCC 4.6.1针对X64
1 2 3 4 5 6 7 8 9 10 11 | // if (x > -1) {... cmp_gt: .seh_endprologue test ecx, ecx js .L2 // if (x >= 0) {... cmp_gte: .seh_endprologue test ecx, ecx js .L5 |
GCC 4.6.1针对x86:
1 2 3 4 5 6 7 8 9 10 11 | // if (x > -1) {... _cmp_gt: mov eax, DWORD PTR [esp+4] test eax, eax js L2 // if (x >= 0) {... _cmp_gte: mov edx, DWORD PTR [esp+4] test edx, edx js L5 |
一般合同条款第4.4.1款:目标武器:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // if (x > -1) {... cmp_gt: .fnstart .LFB0: cmp r0, #0 blt .L8 // if (x >= 0) {... cmp_gte: .fnstart .LFB1: cmp r0, #0 blt .L2 |
IAR 5.20针对手臂皮质-M3:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // if (x > -1) {... cmp_gt: 80B5 PUSH {R7,LR} .... LDR.N R1,??DataTable1 ;; `?<Constant"non-negative">` 0028 CMP R0,#+0 01D4 BMI.N ??cmp_gt_0 // if (x >= 0) {... cmp_gte: 80B5 PUSH {R7,LR} .... LDR.N R1,??DataTable1 ;; `?<Constant"non-negative">` 0028 CMP R0,#+0 01D4 BMI.N ??cmp_gte_0 |
如果你还和我在一起,下面是评估
- MSVC靶向臂使用
cmp r0,#0xFFFFFFFF 作为(x > -1) vscmp r0,#0 作为(x >= 0) 。第一条指令的操作码长两个字节。我想这可能会增加一些时间,所以我们称之为(x >= 0) 的优势。 - 面向x86的MSVC使用
cmp ecx, -1 表示(x > -1) ,而test ecx, ecx 表示(x >= 0) 。第一条指令的操作码长一个字节。我想这可能会增加一些时间,所以我们称之为(x >= 0) 的优势。
注意,GCC和IAR为这两种比较生成了相同的机器代码(可能的例外是使用了寄存器)。因此,根据这项调查,似乎
如果您发现Java或C语言的JET输出有什么不同,我会感到惊讶。我怀疑你会发现任何不同的注意,即使是一个非常小的目标,如8位的AVR。
简而言之,不要担心这种微观优化。我想我在这里写的时间已经超过了在我有生之年执行这些表达式的所有CPU中积累的这些表达式的性能差异所花费的时间。如果您有能力测量性能差异,请将您的努力应用于更重要的事情,如研究亚原子粒子或其他东西的行为。
它在很大程度上依赖于底层架构,但是任何差异都是微不足道的。
如果有的话,我希望
当然,任何明智的编译器都会选择最佳的实现,而不管源代码中是哪个变量。
你的老师一直在读一些非常古老的书。过去有些体系结构缺少
更大的问题是过早的优化。许多人认为写可读代码比写有效代码更重要[1,2]。一旦设计被证明有效,我将把这些优化作为低级库的最后一个阶段来应用。
你不应该一直在考虑以牺牲可读性为代价对代码进行微小的优化,因为这会使代码的阅读和维护更加困难。如果需要进行这些优化,请将它们抽象为较低级别的函数,这样您仍然可以获得更容易为人类阅读的代码。
作为一个疯狂的例子,考虑一个在汇编中编写程序的人,他们愿意放弃额外的效率,并在设计、使用方便和可维护性方面使用Java。
作为补充说明,如果您使用的是C,那么编写一个使用稍微高效的代码的宏可能是一个更可行的解决方案,因为它比分散的操作更能实现效率、可读性和可维护性。
当然,效率和可读性的权衡取决于您的应用程序。如果这个循环每秒运行10000次,那么它可能是一个瓶颈,您可能想花时间优化它,但是如果只是一个单独的语句,偶尔被调用,那么它可能不值得一分钟的时间来获得它。
是的,有区别,你应该看到字节码。
对于
1 2 | if (x >= 0) { } |
字节码是
1 2 | ILOAD 1 IFLT L1 |
对于
1 2 | if (x > -1) { } |
字节码是
1 2 3 | ILOAD 1 ICONST_M1 IF_ICMPLE L3 |
版本1更快,因为它使用特殊的零操作数操作
1 | iflt : jump if less than zero |
但是,可以看到仅在解释模式
1 2 3 4 5 6 7 8 9 |
n=0时显示690 ms,n=1时显示760 ms。(我使用1而不是-1,因为它更容易演示,所以想法保持不变)
事实上,我认为第二个版本应该稍微快一点,因为它需要一个位检查(假设您在0处进行比较,如上面所示)。然而,这种优化从未真正显示,因为大多数编译器将优化此类调用。
">="是单一操作,就像">"。不是两个单独的操作,带有或。
但>=0可能更快,因为计算机只需要检查一个位(负号)。
很抱歉打断了关于表演的谈话。
在我离题之前,让我们注意到,JVM不仅有处理零的特殊指令,而且还有从1到3的常量。有了这一点,体系结构处理零的能力很可能早就失去了,不仅仅是编译器优化,还有字节码到机器代码的转换等等。
我记得在我的x86汇编语言时代,在集合中有大于(
1 2 3 4 5 6 7 8 9 10 11 | ; x >= 0 mov ax, [x] mov bx, 0 cmp ax, bx jae above ; x > -1 mov ax, [x] mov bx, -1 cmp ax, bx ja above |
这些替代方法所用的时间相同,因为指令相同或相似,并且它们消耗可预测的时钟周期数。例如,请参见。
但我来这里是为了离题。
在我面前的答案往往是有针对性的,同时也表明无论你选择哪种方法,你在表现方面都会处于同样的状态。
这就让您可以根据其他标准进行选择。这就是我要做记录的地方。在测试指标时,与
因为从概念上讲,您是在测试一个下限,所以
再次,很抱歉打扰你,但我觉得这比学术界的事情更重要。我总是用这些术语来思考,让编译器担心它认为可以摆脱对常量和运算符严格性的修改的微小优化。
According to this teacher > would be slightly faster then >=. In this
case it was Java, but according to him this also applied for C, c++
and other languages. Is there any truth to this statement?
你的老师根本是错的。不仅"机会"比"0"的速度要快得多,而且因为这种局部优化是由编译器/解释器很好地完成的,而且你会把所有试图帮助的事情都搞砸。毫无疑问,教书不是件好事。
你可以阅读:这个或这个
首先,它高度依赖硬件平台。对于现代PC和ARM SOC,差异主要依赖于编译器优化。但是对于没有FPU的CPU来说,签名数学将是灾难。
例如,简单的8位CPU,如Intel 8008、80488051、Zilog Z80、Motorola 6800,甚至现代RISC PIC或Atmel微处理器都通过8位寄存器的ALU进行所有数学运算,基本上只有进位标志位和Z(零值指示器)标志位。所有严肃的数学都是通过库和表达式完成的。
1 2 | BYTE x; if (x >= 0) |
如果使用JZ或JNZ ASM指令而不调用非常昂贵的库,肯定会赢。