【问题标题】:Which is the faster operation? [closed]哪个操作更快? [关闭]
【发布时间】:2014-07-06 06:47:33
【问题描述】:

我有两个变量 ab。我必须在变量 ab

这是第一种方法:

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

这是第二种方法:

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

更新: 考虑 a 和 b 是无符号的。那么在 逻辑或(||) 之间将花费 更少的执行时间 >算术(+)运算符

这个条件将迭代大约一百万次
对此的任何帮助将不胜感激。

【问题讨论】:

  • 考虑短路。
  • 除非 a 和 b 都是无符号的,否则表达式不等价
  • 查看其中任何一个的组件并告诉我们。
  • 看起来像是过早优化的情况。
  • 看起来像是一个过早的错误案例

标签: c++ c performance


【解决方案1】:

你的第二个条件是错误的。如果a=1, b=-1000,它将评估为false,而您的第一个条件将评估为true。一般来说,你不应该担心这些测试的速度,编译器会优化很多条件,所以逻辑 OR 非常快。一般来说,人们犯的错误比优化这些条件更大……所以除非你真的知道发生了什么,否则不要尝试优化,编译器通常比我们任何人都做得更好。

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

【讨论】:

    【解决方案2】:

    我决定检查 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-42b2,则第一种方法将返回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 中,定义了无符号溢出,因此编译器必须处理整数非常高的情况(尝试 INT_MAX1 for approach2)。即使假设您知道数字不会溢出,也很容易注意到approach1 更快,因为它只是测试两个变量是否都是0

    相信你的编译器,它会比你优化得更好,而且没有你可能不小心写的小错误。编写代码而不是问自己 i++++i 是否更快,或者x >> 1x / 2 是否更快(顺便说一下,x >> 1x / 2 的签名数字不同,因为舍入行为)。

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

    【讨论】:

      【解决方案3】:

      对此的真正答案始终是两者都做,并实际测试哪一个跑得更快。只有这样才能确定。

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

      【讨论】:

        【解决方案4】:

        好吧,我写了一些快速代码并反汇编:

        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 次比较

        真的,哪一个表现更好取决于您的数据是什么样的,以及分支预测器是否可以使用某种模式。如果是这样,我可以想象第一种方法运行得更快,因为处理器基本上“跳过”检查,并且在最好的情况下只需要执行第二个选项的一半操作。不过,老实说,这似乎真的是过早的优化,我敢打赌,您更有可能在代码的其他地方获得更多改进。我发现基本操作大部分时间都不是瓶颈。

        【讨论】:

          【解决方案5】:

          两件事:

          1. (a|b) > 0(a+b) > 0严格地好,所以替换它。

          2. 只有当数字都是无符号时,以上两个才能正常工作。

          【讨论】:

            【解决方案6】:

            如果ab 有可能是负数,则这两个选择不等价,正如@vsoftco 的回答所指出的那样。

            如果ab 都保证是非负整数,我会使用

            if ( (a|b) > 0 )
            

            而不是

            if ( (a+b) > 0 )
            

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

            更新 按位使用| 而不是&

            【讨论】:

            • 使用按位|,您实际上有一个等效于if(a >= 0 && b >= 0 && !(a == 0 && b == 0)) 的if 语句。
            • @Quincunx,在 Linux 机器上由 gcc 4.7.3 为 + 生成的汇编代码只是一行汇编指令 - add。按位| 的汇编代码也是一行汇编指令-or。我不知道你为什么认为按位 | 和你建议的一样复杂。
            • 我并不是说它很复杂。我只是说 if 语句等同于我发布的那个。想想看。如果ab 之一为负,则按位| 的结果为负,因此比较失败。但是,ab 之一可以是 0,并且 if 语句仍然接受它。因此,如果 ab 都是非零整数,则代码有效。我基本上是说((a|b)>0)(a > 0 && b > 0) 非常相似
            • @Quincunx 这就是为什么我在回答前加上“如果ab 都保证是正整数”。
            • 我是说你可以通过说“如果ab 都是非零整数”来改进这个范围。
            猜你喜欢
            • 2021-08-23
            • 1970-01-01
            • 1970-01-01
            • 2011-05-23
            • 1970-01-01
            • 2013-01-09
            • 2020-10-08
            • 2012-07-30
            • 2020-02-12
            相关资源
            最近更新 更多