如何实现一个返回数字绝对值的操作,哪种方法最快?
x=root(x²)
或者if !isPositive(x):
x=x*(-1)
实际上,这个问题可以翻译为,“if
有多快(以及为什么)?”
我的大学编程教授总是告诉我要避免使用if
,因为它们非常慢,但我总是忘记问有多慢和为什么。这里是否有人知道呢?
如何实现一个返回数字绝对值的操作,哪种方法最快?
x=root(x²)
或者if !isPositive(x):
x=x*(-1)
实际上,这个问题可以翻译为,“if
有多快(以及为什么)?”
我的大学编程教授总是告诉我要避免使用if
,因为它们非常慢,但我总是忘记问有多慢和为什么。这里是否有人知道呢?
有一个很棒的技巧可以在不使用if语句的情况下计算2s补码整数的绝对值。理论上来说,如果这个值是负数,你需要切换位并加1,否则你希望按原样传递位。A 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
原则上,您可以仅使用三个汇编指令(无需分支)完成它。而您希望 abs() 函数(通过 math.h 获取)能够以最优方式进行操作。
没有分支 == 更好的性能。与 @paxdiablo 上面的回答相反,在深层流水线中,分支越多,代码中出现分支预测错误和必须回滚的可能性就越大。如果尽可能避免分支,那么您的核心将保持全速运转 :)
value -= temp
,而不是 value += temp & 1
,并且没有理由使用无符号类型的 temp。 - Qwertie((value >> 31) | 1) * value
不就行了吗?乘法并不昂贵。 - M.kazem Akhgary条件语句的速度比简单的算术运算要慢,但比计算平方根等愚蠢的操作快得多。
以下是我从汇编时代总结出来的经验法则:
if
在做什么,并将其编译为只清除浮点数或双精度数的符号位的位运算(像带有 SSE 的 x86 现代 FPU),或者像旧的 x87 fabs
那样的专用指令,在不支持浮点数上的任意位运算的 x87 FPU 上执行相同的操作。 - Peter Cordes或
x = (x<0) ? -x : x;都需要保留负零,因为它与0.0相等。但无论如何,
(-1)*x可以优化为只需使用
xorps`来翻转符号位。 - Peter Cordes计算平方根可能是你能做的最糟糕的事情之一,因为它非常慢。通常有一个库函数可以做到这一点,比如Math.Abs()。与-1相乘也是不必要的;只需返回-x即可。因此,一个好的解决方案如下。
(x >= 0) ? x : -x
编译器可能会将此代码优化为一条指令。由于现代处理器的长执行流水线可能导致条件语句非常昂贵 - 如果分支预测错误并且处理器开始执行错误的代码路径,则必须放弃计算。但是,由于提到的编译器优化,在这种情况下您不需要担心。为了完整起见,在x86系统上用C++进行IEEE浮点数转换的方法如下:
*(reinterpret_cast<uint32_t*>(&foo)) &= 0xffffffff >> 1;
foo
的地址,强制转换为32位无符号整数指针,解引用并应用掩码以保留除了(MSB)符号位之外的所有位。 - awdz9nld-1
的二进制符号,你得到的不是 1
而是一个非常大的值。查找二进制补码以了解原因。 - Julien__如何最快地获取一个数的绝对值
我认为“正确”的答案实际上不在这里。获取绝对值的最快方法可能是使用Intel Intrinsic。请参见https://software.intel.com/sites/landingpage/IntrinsicsGuide/并查找“vpabs”(或另一个适用于您的CPU的内部函数)。我非常确定它会击败这里所有其他解决方案。
如果您不喜欢内部函数(或无法使用它们或...),您可能需要检查编译器是否足够聪明,可以自动将对“本机绝对值”的调用(C ++中的std :: abs 或C#中的
Math.Abs(x))转换为内部函数 - 基本上涉及查看反汇编(已编译)代码。如果您处于JIT中,请确保未禁用JIT优化。
如果上述方法仍未提供优化的指令,您可以使用此处描述的方法:https://graphics.stanford.edu/~seander/bithacks.html#IntegerAbs。
pabsd
非常好用。但是与从整数寄存器复制到XMM再返回相比,neg/cmov更加高效。您几乎总是应该使用std::abs
,并让编译器自动矢量化(如果需要的话),否则请高效地内联它。 - Peter Cordesif
变体通常会在机器级别上转换为条件跳转指令(在表达式的评估后,可能是复杂的,但在这种情况下不是因为它只是一个简单的小于0的检查),因此与平方根相比,它几乎肯定会快得令人眼花缭乱。
计算一个数的平方根可能会慢得多(例如,牛顿法将在机器代码级别使用许多许多个if
语句)。
混淆的主要原因是if
无疑会以非顺序方式改变指令指针。这可能会拖慢预取指令到管道中的处理器,因为当地址意外更改时,它们必须重新填充管道。
然而,与执行平方根操作相比,进行简单的检查和否定的成本微不足道。
你在使用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
(明目张胆地盗窃)
test same,same
而不是or same,same
可以提高效率(在CMP reg,0 vs OR reg,reg中测试寄存器是否为零?)。除非你正在编写真正古老的CPU程序,否则请使用cmov
代替条件分支。 - Peter Cordes计算平方根所需的时间远大于条件判断的时间。如果你曾被教导要避免使用条件语句,因为它们速度较慢,那么你就收到了错误的信息。 与像加减整数或位移这样的简单操作相比,它们确实要慢得多。这就是为什么展开循环只有在做这种简单操作时才有好处的原因。但是从整体上看,条件语句是好的和快速的,而不是坏的和慢的。为了避免条件语句而执行复杂的操作,如调用函数或计算平方根,这是不明智的。
此外,为什么不使用(x = 0-x)而不是(x = x * -1)?也许编译器会对它们进行相同的优化,但第二个方法不管怎样都更简单吧?
i8 num = 10001101 = -13
u8 mask = 01111111 = 127;
i8 res = num & mask = 00001101 = 13
.
我相信这是计算机上计算绝对值的最快方式。如果我错了,请纠正我。