X> -1 VS X> = 0,有没有性能差异(x > -1 vs x >= 0, is

2019-07-18 17:24发布

我听到一个老师曾经下降这一点,并已经过缠着我至今。 比方说,我们要检查整数x大于或等于0。有两种方法来检查:

if (x > -1){
    //do stuff
}

if (x >= 0){
    //do stuff
} 

据这位老师>将略快那么>= 。 在这种情况下,它是Java的,但据他说,这也适用于C,C ++等语言。 是否有任何道理的说法?

Answer 1:

有一个在任何真实世界的感觉没什么区别。

让我们来看看通过各种编译器的各项指标产生一些代码。

  • 我假设一个符号整数操作(这似乎是OP的意图)
  • 我已经调查仅限于C和对我有容易在手的编译器(诚然,一个非常小的样本 - GCC,MSVC和IAR)
  • 基本的优化启用( -O2为GCC, /Ox为MSVC, -Oh为IAR)
  • 使用以下模块:

     void 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靶臂:

// 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和目标64:

// 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和目标86:

// 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目标64

// 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目标86:

// 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

GCC 4.4.1针对ARM:

// 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瞄准了ARM Cortex-M3:

// 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

如果你在我身边的是,这里有任何值得注意的评价之间的差异(x > -1)(x >= 0)显示了:

  • MSVC靶向ARM使用cmp r0,#0xFFFFFFFF(x > -1) VS cmp r0,#0(x >= 0) 第一个指令的操作码是两个字节长。 我想这可能会产生一些额外的时间,所以我们称这种优势为(x >= 0)
  • MSVC靶向x86使用cmp ecx, -1(x > -1) VS test ecx, ecx(x >= 0) 第一个指令的操作码是一个字节长。 我想这可能会产生一些额外的时间,所以我们称这种优势为(x >= 0)

需要注意的是GCC和IAR生成相同机器代码两种比较的(可能的例外是使用寄存器其中)。 所以根据该调查,看来(x >= 0)具有作为“快”的不断所谓轻微的机会。 但无论利用微创短操作码字节编码可能(我强调可能有 )将肯定完全由其他因素所掩盖。

我会感到惊讶,如果你发现任何针对Java或C#的即时编译输出不同。 我怀疑你会发现笔记的任何区别,甚至像一个8位AVR一个非常小的目标。

总之,不用担心这条微优化。 我觉得我写在这里已经花了将在我的有生之年执行它们这些表情在所有的CPU积累的任何性能差异将花更多的时间。 如果你有测量性能上的差异的能力,请申请您的努力,像研究亚原子粒子或某事的行为更重要。



Answer 2:

这是非常依赖于底层架构,但是任何差别将是微不足道的。

如果有的话,我预计(x >= 0)要稍快,与比较0免费的午餐上的一些指令集(如ARM)。

当然,任何理智的编译器会选择最好的实现无论哪个变种是在你的来源。



Answer 3:

你的老师已经读了一些很老的书。 它使用的是与一些架构中缺乏的情况下greater than or equal指令评估>所需的机器周期少于>= ,但这些平台是罕见的,这些天。 我建议去了可读性,并使用>= 0



Answer 4:

这里的一个更大的担忧是过早的优化 。 许多人认为写可读代码比写高效的代码更重要的[ 1 , 2 ]。 一旦设计已经被证明的工作我会在较低水平库应用这些最佳化的最后阶段。

你不应该不断地在考虑可读性的成本在你的代码做微小的优化,因为它会让阅读和维修器材的代码更难。 如果这些最佳化需要采取的地方,抽象他们到较低级别的功能,所以你还是留下的代码更容易阅读的人。

作为一个疯狂的例子,认为人谁在集会的人谁是愿意放弃额外的效率和使用Java它在设计方面的好处,易用性和可维护性写他们的程序。

作为一个侧面说明,如果你使用C,也许写它使用稍微更高效的代码宏是一个更可行的解决方案,因为它会提高效率,可读性和可维护性比分散经营多。

当然还有效率和可读性的取舍取决于你的应用程序。 如果循环运行10000次,第二个则是一个可能的瓶颈,你可能要优化投入时间,但如果它是被称为一个语句偶尔它可能不值得给分增益。



Answer 5:

是的,是有区别的,你应该看到的字节码。

对于

    if (x >= 0) {
    }

字节码

    ILOAD 1
    IFLT L1

对于

if (x > -1) {
}

字节码

ILOAD 1
ICONST_M1
IF_ICMPLE L3

第1版是更快,因为它使用了一个特殊的零操作数操作

iflt : jump if less than zero 

但它是可以看到的差别只在运行的JVM在解释-only模式java -Xint ... ,如本次测试

    int n = 0;       
    for (;;) {
        long t0 = System.currentTimeMillis();
        int j = 0;
        for (int i = 100000000; i >= n; i--) {
            j++;
        }
        System.out.println(System.currentTimeMillis() - t0);
    }

示出了690毫秒n = 0且760毫秒对于n = 1(I 1使用的,而不是-1,因为它更容易证明,这个想法保持相同)



Answer 6:

事实上,我认为第二个版本要稍微快一点,因为它需要一个位检查(假设你在零比较,你看到前面)。 然而这样的优化从来没有真正显示为大多数编译器将优化这样的电话。



Answer 7:

“> =”是单个操作,就像“>”。 有关系2独立操作。

但是,> = 0可能更快,因为计算机需要检查只有一个位(负号)。



Answer 8:

据这位老师>将略快那么> =。 在这种情况下,它是Java的,但据他说,这也适用于C,C ++等语言。 是否有任何道理的说法?

你的老师是根本错误的。 不仅为什么机会比0攀比会sligly快,但由于这种局部优化深受你的编译器/解释器完成的,你可以乱都试图帮助。 明确不是什么好东西来教。

你可以阅读: 本或本



Answer 9:

抱歉这样打断这个谈话有关性能。

我离题之前,让我们注意的是,JVM有特殊说明 ,不仅零处理,而且还常数一通三。 与此说,很可能是该架构来处理零的能力久违的背后不是编译器优化多,但也字节码的机器代码翻译和这样。

我从我的x86汇编语言的天,有两个以上(在设定的指令记住ja )和大于或等于( jae )。 你会做下列操作之一:

; 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

这些替代方案采取的相同的时间量,这是因为指令是相同或相似,并且它们消耗的时钟周期的一个可预测的数量。 见,例如, 这个 。 jajae确实可以检查不同数量的算术寄存器,但支票被需要的指令采取可预见的时间为主。 这又需要保持CPU架构管理。

但我来这里离题。

我以前的答案往往是相关的,并且还指示你会是只要表现而言同一个球场,无论哪一种方式,你选择。

这让你选择基于其他标准。 而这正是我想要做出说明。 当测试指标,宁可紧约束风格检查,主要是x >= lowerBound ,在x > lowerBound - 1 。 这个论点注定是做作,但它归结为可读性,这里一切确实是相等的。

由于概念上你对一个下界,测试x >= lowerBound是规范的试验,结果从代码的读者引起最适合的认知。 x + 10 > lowerBound + 9x - lowerBound >= 0 ,和x > -1都是迂回的方法来测试针对一个下界。

再次,抱歉这样打断,但我觉得像这样超出事物的学者重要。 我一直认为在这些条款,让有关它认为它可以走出与常数和运营商的严格摆弄的微小优化编译器担心。



Answer 10:

首先,它高度依赖于硬件平台。 对于现代PC和ARM SoC的差异主要依靠编译器的优化。 但对于没有FPU的CPU,签署数学将是灾难。

例如简单的8位CPU如Intel 8008,8048,8051,Zilog公司Z80,摩托罗拉6800,甚至现代的RISC PIC或Atmel公司microcontollers做所有数学通过ALU与8位寄存器和已经基本只携带标志位和Z(零值指示)标志位。 所有严肃的数学是通过库完成,并表达

  BYTE x;
  if (x >= 0) 

铁定夺冠,使用JZ或者JNZ汇编指令没有非常昂贵的库调用。



文章来源: x > -1 vs x >= 0, is there a performance difference