这是获得数字绝对值的最快方法

时间:2009-03-20 03:11:15

标签: algorithm performance theory absolute-value

哪种方法是实现返回数字绝对值的操作的最快方法?

x=root(x²)

if !isPositive(x):
    x=x*(-1)

实际上这个问题可以翻译成if有多快(以及为什么请)。

我的大学程序教授总是告诉我要避免if因为他们非常慢,但我总是忘记问为时有多缓慢。这里有人知道吗?

16 个答案:

答案 0 :(得分:75)

有一个很好的技巧来计算2s补码整数的绝对值而不使用if语句。理论上说,如果值为负,则需要切换位并添加一个,否则您希望按原样传递位。 XOR 1碰巧切换A和A XOR 0碰巧保持A完好无损。所以你想要做这样的事情:

  uint32_t temp = value >> 31;     // make a mask of the sign bit
  value ^= temp;                   // toggle the bits if value is negative
  value += temp & 1;               // add one if value was negative

原则上,您可以在三个装配说明(没有分支)的情况下完成。并且您认为使用math.h得到的abs()函数可以最佳地完成它。

没有分支==更好的表现。与@ paxdiablo上面的响应相反,这在深层管道中非常重要,在您的代码中,您拥有的分支越多,您的分支预测器就越有可能出错并且必须回滚等等。如果您避免分支在哪里可能的话,事情将继续在你的核心全力以赴:)。

答案 1 :(得分:59)

条件比简单的算术运算慢,但比计算平方根时更快,更快。

我的集会日的经验法则:

  • 整数或按位运算:1周期
  • 浮点加/ sub / mul:4个周期
  • 浮点div:~30个周期
  • 浮点指数:~200个周期
  • 浮点数sqrt:约60个周期,具体取决于实施
  • 条件分支:平均10个周期,如果预测得好则更好,如果误预测会更糟糕

答案 2 :(得分:27)

呃,你老师真的告诉过你了吗?大多数人遵循的规则是首先使代码可读,然后在证明实际出现问题后再调整任何性能问题。 99.999%的时间你永远不会看到性能问题,因为你使用了太多if语句。 Knuth said it best,“过早优化是万恶之源”。

答案 3 :(得分:11)

计算平方根可能是你可以做的最糟糕的事情之一,因为它真的很慢。通常有一个库函数来执行此操作;像Math.Abs​​()这样的东西。乘以-1也是不必要的;只需返回-x。因此,以下是一个很好的解决方案。

(x >= 0) ? x : -x

编译器可能会将其优化为单个指令。由于执行流程较长,现代处理器上的条件可能相当昂贵 - 如果分支被错误预测并且处理器开始从错误的代码路径执行指令,则必须丢弃计算。但是由于提到的编译器优化,在这种情况下你不需要关心。

答案 4 :(得分:5)

为了完整起见,这里有一种方法可以在C ++的x86系统上实现IEEE浮点数:

*(reinterpret_cast<uint32_t*>(&foo)) &= 0xffffffff >> 1;

答案 5 :(得分:4)

与平方根相比,if变体几乎肯定会盲目快,因为它通常会转换为机器代码级别的条件跳转指令(在表达式评估之后) ,这可能很复杂,但在这种情况下不是,因为它是一个小于0的简单检查。

取一个数字的平方根可能要慢得多(例如,Newton的方法会在机器代码级别使用许多 if语句。

混淆的可能原因是if总是导致以非顺序方式改变指令指针。这会降低预取指令进入管道的处理器,因为当地址意外更改时,它们必须重新填充管道。

然而,与执行平方根操作相比,与简单的检查和否定相比,其成本将是微不足道的。

答案 6 :(得分:3)

  

获取数字绝对值的最快方法是

我认为“正确”的答案实际上并不存在。获得绝对数字的最快方法可能是使用Intel Intrinsic。请参阅https://software.intel.com/sites/landingpage/IntrinsicsGuide/并查找“vpabs”(或其他为您的CPU执行工作的内在函数)。我很确定它会击败所有其他解决方案。

如果您不喜欢内在函数(或者不能使用它们或......),您可能需要检查编译器是否足够聪明以确定是否调用“本机绝对值”(std::abs在C ++中或C#中的Math.Abs(x)将自动更改为内在函数 - 基本上涉及查看反汇编(编译)代码。如果您在JIT中,请确保未禁用JIT优化。

如果这也没有为您提供优化说明,您可以使用此处描述的方法:https://graphics.stanford.edu/~seander/bithacks.html#IntegerAbs

答案 7 :(得分:2)

模运算用于查找余数,表示绝对值。我修改了这个问题,因为它应该是!pos(x)然后x = x * -1。 (没错过)

我不担心if语句的效率。而是专注于代码的可读性。如果您发现存在效率问题,那么请专注于分析代码以找出真正的瓶颈。

如果您想在编码时留意效率,那么您应该只担心算法的大O复杂性。

如果语句非常有效,它会评估任何表达式,然后根据该条件简单地更改program counter。程序计数器存储下一条要执行的指令的地址。

通过-1进行多重复制并检查值是否大于0都可以简化为单个汇编指令。

找到一个数字的根并且首先将该数字平方肯定比具有否定的if更多的操作。

答案 8 :(得分:1)

执行平方根所花费的时间远远大于执行条件所花费的时间。如果你因为速度缓慢而被教导要避免条件,那么你就会被误导。它们比增​​加或减少整数或位移等微不足道的操作要慢得多 - 这就是为什么展开循环只有在你做这些微不足道的操作时才有益。但是在宏观的条件下,条件是好的,快的,不是坏的,也不是慢的。做一些复杂的事情如调用函数或计算平方根以避免条件语句是疯了。

另外,代替(x = x * -1)为什么不做(x = 0-x)?也许编译器会优化它们,但不是第二个更简单吗?

答案 9 :(得分:1)

您使用的是8086组装吗? ; - )

                ; abs value of AX
   cwd          ; replicate the high bit into DX
   xor  ax, dx  ; take 1's complement if negative; no change if positive
   sub  ax, dx  ; AX is 2's complement if it was negative The standard
                : absolute value method works on any register but is much
                ; slower:

   or   bx, bx  ; see if number is negative
   jge  notneg  ; if it is negative...
   neg  bx      ; ...make it positive
notneg:         ; jump to here if positive

(公然stolen

答案 10 :(得分:0)

如果您只是比较两个数字的绝对值(例如,您不需要比较后的任何一个的绝对值),那么只需将两个值平方以使两者都为正(删除每个值的符号), square将大于小方块。

答案 11 :(得分:0)

什么是更快取决于你所针对的编译器和CPU。在大多数CPU和所有编译器上x =(x> = 0)? X:-x;是获得绝对价值的最快方法,但实际上,标准函数通常已经提供了这种解决方案(例如fabs())。它被编译成比较,然后是条件赋值指令(CMOV),而不是条件跳转。有些平台缺乏该指令。虽然,英特尔(但不是微软或GCC)编译器会自动将if()转换为条件赋值,甚至会尝试优化周期(如果可能的话)。

如果CPU使用统计预测,则分支代码通常比条件赋值慢。如果操作多次重复并且条件的结果不断变化,则if()的平均速度可能较慢。像英特尔这样的CPU会开始计算两个分支,并且会丢弃无效的分支,如果大的if()主体或大量循环可能是关键的。

现代Intel CPU上的

sqr()和sqrt()是单个内置指令并且速度不慢,但它们不精确,加载寄存器也需要时间。

相关问题:Why is a CPU branch instruction slow?

最有可能的是,教授希望学生对这个问题进行研究,如果学生能够独立思考并寻找其他来源,那么这个问题只能做得很好,只会很好。

答案 12 :(得分:0)

我在C中为8088/8086做一些复古的图形编程,并且调用abs()非常耗时,所以我用以下代码替换它:

/* assuming 'i' is int; this WILL NOT WORK on floating point */
if (i < 0) {
    i = ~i + 1;
}

这更快的原因是因为它基本上在CALL的程序集中交换了JNE。调用方法会更改几个寄存器,再推几个寄存器,将参数压入堆栈,并可以刷新预取队列。此外,这些操作需要在功能结束时反转,所有这些对CPU来说都非常昂贵。

答案 13 :(得分:0)

有关负数的列表:

如果您的内存中有零存储,只需使用0 - x,其中x是负数。

或者如果您的内存中没有零:

x-x-x,其中x是负数。

或者,为了清楚起见,带有括号:

(x) - (x) - (x) => (-n) - (-n) - (-n),其中x = -n

即从自身中减去负数以得到零,然后从零中减去它。

答案 14 :(得分:0)

为了完整性,如果要处理浮点数,则始终可以执行类似n * sign(n)的操作,其中sign是一个函数,如果数字为正数,则返回+1,如果为负数,则返回-1 。在C语言中,这类似于copysign(1.0, n)(n > 0) - (n < 0)

如今,大多数机器都使用IEEE 754作为浮点格式,因此您可以直接清除符号位:

float fabs(float x) {
    char *c = &x;
    c[0] &= 7;
    return *(float *)c;
}

鉴于abs函数可能会执行此操作,因此最好的选择是在可用时使用它。如果幸运的话,该功能将有几个说明,并且会内联。

答案 15 :(得分:0)

我想知道这个解决方案是否有问题。 有

  • 没有分支
  • 无位宽相关移位
  • 一点也不乱
  • 无架构依赖
  • 无编译器依赖
  • 可选:INT_MIN 没有未定义的行为

也许指令太多?

我的解决方案

xabs = (x < 0)*(-x) + (x >=0)*x
  • 2 个整数比较
  • 2 次乘法

旧解决方案

xtest = (x < 0)*x;           // xtest = x if is negative, otherwise zero
xabs = (x - xtest) - xtest;  // Order of instructions taken into account

否定 INT_MIN

的未定义行为

可以添加对未定义行为的检查(否定 INT_MIN), 如果您的价值在之前的算法中不受限制。 但这使它变得有点复杂。 也许,有人找到了更简单的逻辑。

xabs =   (x < -INT_MAX)*INT_MAX            //  x < -INT_MAX < 0  --> xabs = INT_MAX
       + ((x >= -INT_MAX)&&(x < 0))*(-x)   // -INT_MAX =< x < 0  --> xabs = -x
       + (x >= 0)*x                        // 0 <= x             --> xabs = +x
  • 5 次整数比较
  • 3 次整数乘法

不幸的是,我从未做过速度比较。 所以不知道是不是真的比

if ( x < 0 )
{
  if ( x >= -INT_MAX )
  {
    x = -x;
  }
  else
  {
    x = INT_MAX;
  }
}