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

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

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

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

根据这位老师的说法,那会稍微快一点。在这种情况下,它是Java,但据他说,这也适用于C,c ++和其他语言。这种说法有道理吗?>>=


答案 1

它在很大程度上取决于底层架构,但任何差异都将是微不足道的。

如果有的话,我预计会稍微快一点,因为在某些指令集(如ARM)上免费比较。(x >= 0)0

当然,任何明智的编译器都会选择最佳的实现,而不管源代码中是哪个变体。


答案 2

在任何现实世界的意义上都没有区别。

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

  • 我假设一个签名的int操作(这似乎是OP的意图)
  • 我通过调查将C和我手头的编译器限制在一起(诚然,这是一个非常小的样本 - GCC,MSVC和IAR)
  • 启用基本优化(针对 GCC、MSVC、IAR)-O2/Ox-Oh
  • 使用以下模块:

    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 针对 ARM:

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

面向 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

面向 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

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

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

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

请注意,GCC和IAR为两种比较生成了相同的机器代码(可能除了使用哪个寄存器)。因此,根据这项调查,它似乎有一个非常小的机会“更快”。但是,无论最小较短的操作码字节编码可能具有什么优势(我强调可能具有)肯定会被其他因素完全掩盖。(x >= 0)

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

简而言之,不要担心这种微优化。我认为我在这里写的文章已经花费了更多的时间,而不是在我有生之年执行它们的所有CPU上累积的这些表达式的性能的任何差异。如果你有能力测量性能的差异,请把你的努力放在更重要的事情上,比如研究亚原子粒子的行为。