哪种方法是实现返回数字绝对值的操作的最快方法?
x=root(x²)
或
if !isPositive(x):
x=x*(-1)
实际上这个问题可以翻译成if
有多快(以及为什么请)。
我的大学程序教授总是告诉我要避免if
因为他们非常慢,但我总是忘记问为时有多缓慢。这里有人知道吗?
答案 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)
条件比简单的算术运算慢,但比计算平方根时更快,更快。
我的集会日的经验法则:
答案 2 :(得分:27)
答案 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()是单个内置指令并且速度不慢,但它们不精确,加载寄存器也需要时间。
最有可能的是,教授希望学生对这个问题进行研究,如果学生能够独立思考并寻找其他来源,那么这个问题只能做得很好,只会很好。答案 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
旧解决方案
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
不幸的是,我从未做过速度比较。 所以不知道是不是真的比
if ( x < 0 )
{
if ( x >= -INT_MAX )
{
x = -x;
}
else
{
x = INT_MAX;
}
}