x> -1 vs x> = 0,是否存在性能差异

Che*_*ron 36 c++ java operators premature-optimization micro-optimization

我听过一位老师放弃了这一次,从那以后一直困扰着我.假设我们要检查整数x是否大于或等于0.有两种方法可以检查:

if (x > -1){
    //do stuff
}
Run Code Online (Sandbox Code Playgroud)

if (x >= 0){
    //do stuff
} 
Run Code Online (Sandbox Code Playgroud)

根据这个老师>会稍快一点>=.在这种情况下它是Java,但据他说,这也适用于C,c ++和其他语言.这句话有什么道理吗?

Gra*_*and 29

它非常依赖于底层架构,但任何差异都是微不足道的.

如果有的话,我希望(x >= 0)稍快一些,因为0在某些指令集(例如ARM)上免费提供.

当然,任何合理的编译器都会选择最佳实现,无论您的源代码是哪种变体.


Mic*_*urr 29

任何现实世界的意义都没有区别.

让我们看看各种编译器为各种目标生成的一些代码.

  • 我假设一个有符号的int操作(这似乎是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");
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

以下是他们为比较操作生成的内容:

针对ARM的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|
Run Code Online (Sandbox Code Playgroud)

针对x64的MSVC 11:

// 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
Run Code Online (Sandbox Code Playgroud)

针对x86的MSVC 11:

// 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
Run Code Online (Sandbox Code Playgroud)

GCC 4.6.1针对x64

// if (x > -1) {...
cmp_gt:
    .seh_endprologue
    test    ecx, ecx
    js  .L2

// if (x >= 0) {...
cmp_gte:
    .seh_endprologue
    test    ecx, ecx
    js  .L5
Run Code Online (Sandbox Code Playgroud)

GCC 4.6.1针对x86:

// 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
Run Code Online (Sandbox Code Playgroud)

针对ARM的GCC 4.4.1:

// if (x > -1) {...
cmp_gt:
    .fnstart
.LFB0:
    cmp r0, #0
    blt .L8

// if (x >= 0) {...
cmp_gte:
    .fnstart
.LFB1:
    cmp r0, #0
    blt .L2
Run Code Online (Sandbox Code Playgroud)

针对ARM Cortex-M3的IAR 5.20:

// 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
Run Code Online (Sandbox Code Playgroud)

如果你还和我在一起,那么评估(x > -1)(x >= 0)显示之间的差异就是:

  • 针对ARM的MSVC cmp r0,#0xFFFFFFFF用于(x > -1)vs cmp r0,#0for (x >= 0).第一条指令的操作码长两个字节.我想这可能会引入一些额外的时间,所以我们称之为优势(x >= 0)
  • 针对x86的MSVC cmp ecx, -1用于(x > -1)vs test ecx, ecxfor (x >= 0).第一条指令的操作码长一个字节.我想这可能会引入一些额外的时间,所以我们称之为优势(x >= 0)

请注意,GCC和IAR为两种比较生成了相同的机器代码(可能的例外是使用了哪个寄存器).因此,根据这项调查,看起来(x >= 0)"快"的可能性微乎其微.但是,最小的操作码字节编码可能具有的优点(我强调可能有)将完全被其他因素所掩盖.

如果您发现Java或C#的jitted输出有任何不同,我会感到惊讶.我怀疑即使对于像8位AVR这样的非常小的目标,你也会发现任何差异.

总之,不要担心这种微观优化.我认为我在这里写的内容已经花费了更多的时间,而不是在我生命中执行它们的所有CPU中累积的这些表达式的性能差异所花费的时间.如果您有能力衡量性能差异,请将您的努力应用于更重要的事情,例如研究亚原子粒子的行为.


das*_*ght 19

你的老师一直在读一些很旧的书.过去一些架构缺乏greater than or equal评估>所需机器周期少的指令>=,但这些平台现在很少见.我建议寻求可读性和使用>= 0.

  • @Cheiron:编译器已经有一百万年了,无法发现优化. (3认同)
  • @Cheiron甚至ATMEL的8位AVR都有`BRGE`(分支如果大于或等于)和'BRSH`(分支如果相同或更高)指令,所以你看不出任何区别. (2认同)

Ara*_*yan 13

这里更大的问题是过早优化.许多人认为写可读代码比写更重要的高效的码[ 1,2 ].一旦设计被证明有效,我会将这些优化应用为低级库中的最后一个阶段.

您不应该一直考虑以可读性为代价在代码中进行微小的优化,因为它会使读取和维护代码变得更加困难.如果需要进行这些优化,请将它们抽象为较低级别的函数,这样您仍然可以使用更容易为人类阅读的代码.

作为一个疯狂的例子,考虑一下将他们的程序编写成一个愿意放弃额外效率并使用Java在设计,易用性和可维护性方面获益的人.

作为旁注,如果您正在使用C,或许编写一个使用稍微更高效的代码的宏是一种更可行的解决方案,因为它将比分散操作更高效地实现效率,可读性和可维护性.

当然,效率和可读性的权衡取决于您的应用.如果该循环每秒运行10000次,那么它可能是瓶颈,你可能想花时间优化它,但如果它是一个单独的声明,偶尔会调用它可能不值得它获得微小的收益.


Evg*_*eev 9

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

对于

    if (x >= 0) {
    }
Run Code Online (Sandbox Code Playgroud)

字节码是

    ILOAD 1
    IFLT L1
Run Code Online (Sandbox Code Playgroud)

对于

if (x > -1) {
}
Run Code Online (Sandbox Code Playgroud)

字节码是

ILOAD 1
ICONST_M1
IF_ICMPLE L3
Run Code Online (Sandbox Code Playgroud)

版本1更快,因为它使用特殊的零操作数操作

iflt : jump if less than zero 
Run Code Online (Sandbox Code Playgroud)

但是有可能看到仅在仅解释模式下运行JVM的差异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);
    }
Run Code Online (Sandbox Code Playgroud)

对于n = 0显示690 ms,对于n = 1显示760 ms.(我使用1代替-1,因为它更容易演示,这个想法保持不变)

  • 你开启了优化吗?JIT会不会优化它吗? (6认同)
  • 哇,老师在“哪个更快”上也错了:) (2认同)
  • 使用`-Xint`进行基准测试毫无意义. (2认同)