这是更快的操作

Which is the faster operation?

本文关键字:操作      更新时间:2023-10-16

我有两个变量ab。我必须在变量ab上写一个if条件:

This is First Approach:

if(a > 0 || b >0){
    //do some things
}

这是第二个方法:

if((a+b) > 0){
    //do some thing
}

Update:考虑a和b是无符号的。逻辑或(||)算术(+)运算符

这个条件将围绕迭代一百万次
对此任何帮助都将不胜感激。

你的第二个条件是错误的。如果是a=1, b=-1000,它会求值为false,而第一个条件会求值为true。一般来说,您不应该担心这种测试的速度,编译器会对条件进行大量优化,因此逻辑或非常快。总的来说,比起优化这些条件,人们犯了更大的错误……所以不要尝试优化,除非你真的知道发生了什么,编译器通常比我们做得好得多。

原则上,在第一个表达式中,你有2个CMP和一个OR,而在第二个表达式中,你只有一个CMP和一个ADD,所以第二个应该更快(即使编译器在第一种情况下做一些短路,但这不能100%发生),但是在你的情况下表达式是不相等的(好吧,它们是正数…)。

我决定为C语言检查这一点,但是相同的参数适用于c++,类似的参数适用于Java(除了Java允许有符号溢出)。测试了以下代码(对于c++,将_Bool替换为bool)。

_Bool approach1(int a, int b) {
    return a > 0 || b > 0;
}
_Bool approach2(int a, int b) {
    return (a + b) > 0;
}

这导致了拆解。

    .file   "faster.c"
    .text
    .p2align 4,,15
    .globl  approach1
    .type   approach1, @function
approach1:
.LFB0:
    .cfi_startproc
    testl   %edi, %edi
    setg    %al
    testl   %esi, %esi
    setg    %dl
    orl %edx, %eax
    ret
    .cfi_endproc
.LFE0:
    .size   approach1, .-approach1
    .p2align 4,,15
    .globl  approach2
    .type   approach2, @function
approach2:
.LFB1:
    .cfi_startproc
    addl    %esi, %edi
    testl   %edi, %edi
    setg    %al
    ret
    .cfi_endproc
.LFE1:
    .size   approach2, .-approach2
    .ident  "GCC: (SUSE Linux) 4.8.1 20130909 [gcc-4_8-branch revision 202388]"
    .section    .note.GNU-stack,"",@progbits
即使考虑到现在的编译器有多聪明,

这些代码也有很大的不同。为什么会这样呢?嗯,原因很简单——它们并不完全相同。如果a-42, b2,第一种方法将返回true,第二种方法将返回false

当然,你可能认为ab应该是无符号的。

    .file   "faster.c"
    .text
    .p2align 4,,15
    .globl  approach1
    .type   approach1, @function
approach1:
.LFB0:
    .cfi_startproc
    orl %esi, %edi
    setne   %al
    ret
    .cfi_endproc
.LFE0:
    .size   approach1, .-approach1
    .p2align 4,,15
    .globl  approach2
    .type   approach2, @function
approach2:
.LFB1:
    .cfi_startproc
    addl    %esi, %edi
    testl   %edi, %edi
    setne   %al
    ret
    .cfi_endproc
.LFE1:
    .size   approach2, .-approach2
    .ident  "GCC: (SUSE Linux) 4.8.1 20130909 [gcc-4_8-branch revision 202388]"
    .section    .note.GNU-stack,"",@progbits

很容易注意到approach1在这里更好,因为它没有做无意义的加法,这实际上是完全错误的。事实上,它甚至对(a | b) != 0进行了优化,这是正确的优化。

在C中,定义了无符号溢出,所以编译器必须处理整数非常大的情况(尝试approach2INT_MAX1)。即使假设您知道数字不会溢出,也很容易注意到approach1更快,因为它只是测试两个变量是否都是0

相信你的编译器,它会比你优化得更好,而且没有你可能不小心写出来的小bug。写代码时不要问自己i++++i哪个更快,或者x >> 1x / 2哪个更快(顺便说一下,x >> 1x / 2对于有符号的数字做的事情不同,因为四舍五入的行为)。

如果你想优化一些东西,优化你使用的算法。而不是使用最坏情况O(N4)排序算法,使用最坏情况O(N log N)算法。这实际上会使程序更快,特别是如果您对相当大的数组进行排序

真正的答案总是两者都做,并实际测试哪一个运行得更快。这是唯一能确定的方法。

我猜第二个会运行得更快,因为添加是一个快速的操作,但是错过的分支会导致管道清理和各种讨厌的事情。但这是数据相关的。但它并不完全相同,如果a或b被允许为负或大到足以溢出,那么它就不是相同的测试。

我编写了一些快速代码并进行了反汇编:

public boolean method1(final int a, final int b) {
    if (a > 0 || b > 0) {
        return true;
    }
        return false;
}
public boolean method2(final int a, final int b) {
    if ((a + b) > 0) {
        return true;
    }
        return false;
}
这些生产:

public boolean method1(int, int);
  Code:
     0: iload_1       
     1: ifgt          8
     4: iload_2       
     5: ifle          10
     8: iconst_1      
     9: ireturn       
    10: iconst_0      
    11: ireturn       
public boolean method2(int, int);
  Code:
     0: iload_1       
     1: iload_2       
     2: iadd          
     3: ifle          8
     6: iconst_1      
     7: ireturn       
     8: iconst_0      
     9: ireturn       
如你所见,它们非常相似;唯一的区别是执行> 0测试与a + b;看起来||被优化了。我不知道JIT编译器对它们进行了什么优化。

如果你想让真的挑剔:

选项1:总是1加载1比较,可能2加载2比较

选项2:总是2个加载,1个添加,1个比较

所以,哪一个表现更好取决于你的数据看起来像什么,以及是否有分支预测器可以使用的模式。如果是这样,我可以想象第一个方法运行得更快,因为处理器基本上"跳过"检查,在最好的情况下,只需要执行第二个选项的一半操作。不过,说实话,这看起来确实像是不成熟的优化,我愿意打赌,您更有可能在代码的其他地方获得更多改进。我不认为基本操作在大多数情况下是瓶颈。

两件事:

  1. (a|b) > 0严格优于 (a+b) > 0,所以替换它

如果ab有可能是负数,这两个选择是不相等的,正如@vsoftco的答案所指出的。

如果ab都保证为非负整数,我将使用

if ( (a|b) > 0 )
不是

if ( (a+b) > 0 )

我认为按位|比整数加法快。

使用按位|代替&