通过位操作更快地实现 Math.abs()
Posted
技术标签:
【中文标题】通过位操作更快地实现 Math.abs()【英文标题】:Faster implementation of Math.abs() by bit-manipulation 【发布时间】:2013-10-29 10:38:46 【问题描述】:Math.abs(x)
的正常实现(由 Oracle 实现)由下式给出
public static double abs(double a)
return (a <= 0.0D) ? 0.0D - a : a;
将数字符号的一位编码设置为零(或一)不是更快吗? 我想只有一位编码数字的符号,并且总是相同的位,但我可能错了。
或者我们的计算机通常不适合使用原子指令对单个位进行操作?
如果可以实现更快的实现,你能给它吗?
编辑:
有人向我指出,Java 代码是独立于平台的,因此它不能依赖于单台机器的原子指令。然而,为了优化代码,JVM 热点优化器确实会考虑机器的具体情况,并且可能会应用正在考虑的优化。
然而,通过一个简单的测试,我发现至少在我的机器上,Math.abs
函数似乎没有针对单个原子指令进行优化。我的代码如下:
long before = System.currentTimeMillis();
int o = 0;
for (double i = 0; i<1000000000; i++)
if ((i-500)*(i-500)>((i-100)*2)*((i-100)*2)) // 4680 ms
o++;
System.out.println(o);
System.out.println("using multiplication: "+(System.currentTimeMillis()-before));
before = System.currentTimeMillis();
o = 0;
for (double i = 0; i<1000000000; i++)
if (Math.abs(i-500)>(Math.abs(i-100)*2)) // 4778 ms
o++;
System.out.println(o);
System.out.println("using Math.abs: "+(System.currentTimeMillis()-before));
这给了我以下输出:
234
using multiplication: 4985
234
using Math.abs: 5587
假设乘法是由原子指令执行的,似乎至少在我的机器上,JVM 热点优化器不会将 Math.abs
函数优化为单指令操作。
【问题讨论】:
使用 64 位 JVM 或-server
选项尝试您的基准测试。在我的机器上,abs
比乘法快。
同上。我也做了测试。 abs 对我来说更快
【参考方案1】:
我的第一个想法是,这是因为 NaN
(非数字)值,即如果输入是 NaN
,它应该会在没有任何更改的情况下返回。但这似乎不是必需的,因为 harold 的测试表明 JVM 的内部优化不会保留 NaN 的符号(除非您使用 StrictMath
)。
Math.abs 的documentation 说:
换句话说,结果与表达式的值相同:
Double.longBitsToDouble((Double.doubleToLongBits(a)<<1)>>>1)
所以这个类的开发者知道位操作的选项,但他们决定不这样做。
很可能,因为优化此 Java 代码毫无意义。在大多数环境中,一旦在热点中遇到它,热点优化器将用适当的 FPU 指令替换它的调用。许多java.lang.Math
方法以及Integer.rotateLeft
和类似方法都会发生这种情况。它们可能有一个纯 Java 实现,但如果 CPU 有一条指令,它就会被 JVM 使用。
【讨论】:
真的需要不加修改地返回 NaN 吗?毕竟,符号改变的 NaN 仍然是 NaN.. 好的,显然NaN的符号是not preserved byMath.abs
,所以无条件清除符号位是完全有效的
所以你有一个很好的证明 java.lang.Math.abs
内部的 Java 代码并没有真正执行(因为它会保留符号)而是被一个内部函数替换。您可以将结果与java.lang.StrictMath.abs
进行比较...
好的。你是对的。 OP 询问了常规的Math.abs
虽然
看来热点优化器并没有真正做好工作,因为它似乎需要不止一次的操作才能达到绝对值。在我刚刚运行的测试中,对于某些表达式 [1] 和 [2],Math.abs([1]) > Math.abs([2])
比 [1] * [1] > [2] * [2]
慢。【参考方案2】:
我不是 java 专家,但我认为问题在于这个定义可以用语言表达。浮点数上的位操作是特定于机器格式的,因此不可移植,因此在 Java 中是不允许的。我不确定是否有任何 jit 编译器会进行优化。
【讨论】:
在 Java 中,格式被很好地指定(大多数 FPU 无论如何都使用这种格式)。见doubleToRawLongBits以上是关于通过位操作更快地实现 Math.abs()的主要内容,如果未能解决你的问题,请参考以下文章