【问题标题】:Efficency of Comparisons in C++? ( abs(X)>1 vs abs(x) != 0)C ++中比较的效率? ( abs(X)>1 vs abs(x) != 0)
【发布时间】:2009-07-10 04:48:25
【问题描述】:

我知道 - 过早的优化。
但是我有一些代码应该可以查明位置是否更改与缓存位置。

当前代码是:

if(abs(newpos-oldpos) > 1){
    .....
}

使用以下是否更有效率?

if(abs(newpos-oldpos) != 0){
    ....
}

为什么或为什么不?我目前正在讨论它是否更具可读性,并且想知道我是否缺少性能差异。

【问题讨论】:

  • 澄清-所有头寸都是多头(因此 abs() 而不是 fabs())
  • 我假设你的意思是 > 0 而不是 > 1 否则它不是同一个测试?反正。你的编译器比你聪明。它会将您使用的任何形式转换为最有效的汇编代码。(编译器很聪明,他们不只是逐字将 c++/C 代码翻译成汇编)

标签: c++ optimization comparison performance


【解决方案1】:

为什么不这样?

if (newpos != oldpos) {
    ...
}

由于缺少abs(),比两者都更有效,并且启动更清晰。

【讨论】:

  • (newpos != oldpos) 与 (abs(newpos-oldpos) > 1) 不同。
  • 但是和(abs(newpos-oldpos)!=0)是一样的。问题是关于相等性的,要么其中一段代码是错误的(我的赌注是第一个),要么 Luciano 有比比较运算符的效率更大的问题。
  • 我同意 (newpos != oldpos) 在各方面都优于 (abs(newpos-oldpos) != 0)。但是,我认为将 (abs(newpos-oldpos) >1) 加入组合会改变答案应该是什么(特别是因为它是您可能想要保留其行为的原始代码)。
【解决方案2】:

不改变的主要原因

(abs(newpos-oldpos) > 1)

(abs(newpos-oldpos) != 0)

是它们在语义上不同。

abs(newpos-oldpos) == 1 你得到不同的结果。这是一个示例,说明为什么您应该“仅仅因为”而不愿意更改事物 - 除了无论如何性能不会有可衡量的差异(也可能没有实际差异)。

【讨论】:

  • 是的,我现在明白了。这个概念验证(单位变化)的语义已经发生了变化,所以我真的希望它把它改成 0 的增量......我将不得不在这个代码更改上投入比我更多的时间本来计划的,想多了,现在也许可以完全去掉这个条件了。
【解决方案3】:

如果你删除了不必要的 abs(),第二个效率会更高。如果您要与零进行比较,则差异是正数还是负数都没有关系。另外,我认为它更具可读性。但是,这两个条件似乎并不等价。如果 abs(newpos-oldpos) == 1 会发生什么?

【讨论】:

    【解决方案4】:

    在大多数架构上,应该没有区别。比较通常在 CPU 内部进行,方法是做减法并让 ALU 设置条件代码。分支是通过测试条件代码来完成的(即,不等于分支测试条件代码寄存器中的零位,分支通常测试零、负和溢出标志)。

    【讨论】:

    • 除了与零比较,在这种情况下,前面的指令经常已经适当地设置了标志,不会发出减法。
    【解决方案5】:

    除非我遗漏了什么,否则他们不会做同样的事情。 x > 1 与 x != 0 不同。

    【讨论】:

    • abs 永远不会小于零。因此,abs(x) != 0 意味着 abs(x) >= 1。但是,使用 > 是一个错误 :)
    • 感谢您向我解释 abs() 的行为,碰巧我已经知道了。
    【解决方案6】:

    不要尝试将比较运算符优化为相等运算符;它们应该具有相同的时序,并且只有整数具有相同的值。

    如果您要优化,请尝试

    if ((newpos - oldpos > 1) || (oldpos - newpos > 1))
    

    这仍然是可读的。 (以及对于浮动 pt #s 始终正确)

    编辑: 确认!没关系,如果您想知道位置是否已经改变了一些最小增量(我最初从字面上阅读了您的代码问题,没有看到您要实现的总体目标),请使用:

    if ((newpos - oldpos > delta) || (oldpos - newpos > delta))
    

    对于 delta > 0,或者这个(正如 Noah M 建议的那样)

    if (newpos != oldpos)
    

    对于 delta = 0

    【讨论】:

    • 避免 abs 的开销可能是一个更快的解决方案。
    【解决方案7】:

    忽略它们不是等效操作的事实,在 x86 上,您可能可以节省一个周期左右。

    abs(新位置) > 1

    1. 减法
    2. 绝对值
    3. 与 1 相比
    4. 跳转

    abs(newpos-oldpos) != 0

    1. 减法
    2. 绝对值
    3. Jmp - 如果 abs 是内联的并且最后一个操作适当地设置了零标志。

    如果这对您的程序有任何可衡量的影响,我会感到惊讶 - 如果您的代码已经运行得如此紧凑,那么您绝对值得称赞。

    【讨论】:

      【解决方案8】:

      由于答案取决于您的架构,让我们看看在 x86-64(使用 gcc -O3)上生成的代码:

      #include <math.h>
      
      int t_gt(int x) { // note! not equivalent to the others
          return abs(x) > 1;
      }
      
      int t_ge(int x) {
          return abs(x) >= 1;
      }
      
      int t_ne(int x) {
          return abs(x) != 1;
      }
      

      变成:

      Disassembly of section .text:
      
      0000000000000000 <t_gt>:
      #include <math.h>
      
      int t_gt(int x) {
         0:   89 f8                   mov    %edi,%eax
         2:   c1 f8 1f                sar    $0x1f,%eax
         5:   31 c7                   xor    %eax,%edi
         7:   29 c7                   sub    %eax,%edi
         9:   31 c0                   xor    %eax,%eax
         b:   83 ff 01                cmp    $0x1,%edi
         e:   0f 9f c0                setg   %al
          return abs(x) > 1;
      }
        11:   c3                      retq   
        12:   66 66 66 66 66 2e 0f    nopw   %cs:0x0(%rax,%rax,1)
        19:   1f 84 00 00 00 00 00 
      
      0000000000000020 <t_ge>:
      
      int t_ge(int x) {
        20:   89 f8                   mov    %edi,%eax
        22:   c1 f8 1f                sar    $0x1f,%eax
        25:   31 c7                   xor    %eax,%edi
        27:   29 c7                   sub    %eax,%edi
        29:   31 c0                   xor    %eax,%eax
        2b:   85 ff                   test   %edi,%edi
        2d:   0f 9f c0                setg   %al
          return abs(x) >= 1;
      }
        30:   c3                      retq   
        31:   66 66 66 66 66 66 2e    nopw   %cs:0x0(%rax,%rax,1)
        38:   0f 1f 84 00 00 00 00 
        3f:   00 
      
      0000000000000040 <t_ne>:
      
      int t_ne(int x) {
        40:   89 f8                   mov    %edi,%eax
        42:   c1 f8 1f                sar    $0x1f,%eax
        45:   31 c7                   xor    %eax,%edi
        47:   29 c7                   sub    %eax,%edi
        49:   31 c0                   xor    %eax,%eax
        4b:   83 ff 01                cmp    $0x1,%edi
        4e:   0f 95 c0                setne  %al
          return abs(x) != 1;
      }
        51:   c3                      retq   
      

      如您所见,有两个区别:

      • set* 操作上的条件代码不同。这可能不会影响速度。
      • 对于 t_ge,使用两字节寄存器测试 (AND),而其他两个使用 cmp(减法)和文字单字节操作数进行比较。

      虽然各种 set* 变体之间以及 test 和 cmp 之间的差异可能为零,但 cmp 的附加一字节操作数可能会大大降低性能。

      当然,完全摆脱无意义的 abs() 可以获得最佳性能:

      0000000000000060 <t_ne2>:
      
      int t_ne2(int x) {
        60:   31 c0                   xor    %eax,%eax
        62:   85 ff                   test   %edi,%edi
        64:   0f 95 c0                setne  %al
          return (x != 0);
      }
        67:   c3                      retq   
      

      请记住,这些发现可能不适用于其他架构;但是在任何地方失去腹肌肯定会更快。

      【讨论】:

        【解决方案9】:

        性能差异微乎其微,但第一个会更有效(根据我的猜测)b/c 它涉及的操作比 != 少。此外,这 2 个语句的含义不同,例如,尝试 abs(newpos - oldpos) = 0.5 看看,除非这两个变量是整数。

        【讨论】:

        • 我不这么认为:我不认为它涉及的操作比 != 少。
        • 在 x86 上,比较设置指示零、大于等的标志 - 所以它们在成本方面是相同的
        【解决方案10】:

        与其猜测编译器会做什么,为什么不直接查看生成的汇编代码,或者测量其执行情况?

        【讨论】:

          【解决方案11】:

          如果是我遗漏了什么,我现在不知道,但该代码在每本关于浮点数的书中都有,它旨在使两个略有不同的数字之间得到正匹配。

          如果代码必须比较两个浮点数,则在大多数机器上不可能进行优化,但重要的是这不是过早优化,而是重构您不完全理解的代码。

          【讨论】:

            【解决方案12】:

            至少在我目前使用的编译器(gcc 4.2)上,它为您的第一个表达式生成的汇编代码采用了here 描述的技巧。然后它递减结果并使用条件代码来决定如何分支。

            第二次,它会将其重写为基本上是newpos != oldpos

            你给出的两个表达的意思略有不同。但无论哪种方式,我见过的最合理的编译器都会部署一些非常有趣的技巧来微优化您的代码。在这方面你很难超越编译器。最好的选择是通常的建议:尝试两个版本,分析代码,看看哪个版本执行得更快。

            顺便说一句,如果你的意思是abs(newpos - oldpos) &gt;= 1 用于第一次测试,它仍然会生成绝对值序列。假设是二进制补码系统,这可能是因为减法中可能溢出。例如,在我的机器上,abs(-2147483648 - 2147483647) 给出 1,如果您正在寻找 2 或更多的增量,即使它们明显不同,它也会使您的测试失败。即使在极端情况下,编译器的优化器也必须小心保持这种行为。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2020-02-14
              • 1970-01-01
              • 2014-02-18
              • 1970-01-01
              • 2023-02-17
              • 2012-07-05
              • 2011-11-20
              相关资源
              最近更新 更多