C ++中的比较效率? (abs(X)> 1 vs abs(x)!= 0)

时间:2009-07-09 18:52:50

标签: c++ optimization comparison performance

我知道 - 过早优化。
但是我有代码可以找出一个位置是否与缓存位置相比发生了变化。

目前的代码是:

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

使用以下内容是否更有效?

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

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

12 个答案:

答案 0 :(得分:13)

为什么不呢?

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

由于缺少abs()而更加高效,并且更加清晰。

答案 1 :(得分:13)

不改变的主要原因

(abs(newpos-oldpos) > 1)

(abs(newpos-oldpos) != 0)

它们在语义上是不同的。

abs(newpos-oldpos) == 1得到不同的结果时。这就是为什么你不应该仅仅因为'而不愿改变事物的一个例子 - 除了事实上无论如何都没有可衡量的表现差异(也可能没有实际差异)。

答案 2 :(得分:4)

如果删除不必要的abs(),第二个效率会更高。如果您与零进行比较,则差异是正面还是负面并不重要。另外,我认为它更具可读性。但是,这两个条件看起来并不相同;如果abs(newpos-oldpos)== 1?

会发生什么

答案 3 :(得分:3)

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

答案 4 :(得分:3)

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

答案 5 :(得分:2)

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

如果您要进行优化,请尝试

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

仍然可读。 (以及对浮动pt #s的一致性)

修改 确认!没关系,如果你想知道位置是否已经改变了一些最小的delta(我最初读的是你的代码问题没有看到你想要实现的总体目标),请使用:

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

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

if (newpos != oldpos)

表示delta = 0

答案 6 :(得分:1)

忽略它们不是等效操作的事实,在x86上,您可能能够保存一个左右的循环。

abs(new-pos)> 1

将是

  1. 减法
  2. 阿布斯
  3. 与1
  4. 比较
  5. JMP
  6. abs(newpos-oldpos)!= 0

    将是

    1. 减法
    2. 阿布斯
    3. Jmp - 如果abs被内联并且最后一个操作适当地设置了零标志。
    4. 如果这会对您的程序产生任何可衡量的影响,我会感到惊讶 - 如果您的代码已经运行得非常紧张,那么您肯定值得赞扬。

答案 7 :(得分:1)

由于答案取决于您的架构,让我们看一下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 * ops上的条件代码不同。这可能不会影响速度。
  • 对于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   

请注意,这些调查结果可能不适用于其他架构;然而,失去腹肌肯定会更快。

答案 8 :(得分:0)

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

答案 9 :(得分:0)

不是猜测编译器会做什么,为什么不直接查看生成的汇编代码,还是测量它的执行?

答案 10 :(得分:0)

我现在不知道是不是我错过了什么,但是每本关于浮点数的书都有这个代码,并且它旨在得到两个略有不同的数字之间的正匹配。

如果代码必须比较两个浮点数,那么在大多数机器上都没有可能的优化,但重要的是这不是关于过早优化,而是关于重构你不完全理解的代码。

答案 11 :(得分:0)

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

对于你的第二个,它将它重写为基本上是newpos != oldpos

的东西

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

顺便说一句,如果您在第一次测试中意味着abs(newpos - oldpos) >= 1,它仍会生成绝对值序列。这可能是因为假设有一个二进制补码系统,减法中可能会出现溢出。例如,在我的机器上,abs(-2147483648 - 2147483647)给出1,如果你正在寻找说2,或更多的delta,即使它们显然是完全不同的,那么你的测试也会失败。即使在边缘情况下,编译器的优化器也必须小心保留这种行为。