通过位运算更快地实现Math.abs()函数

3

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 hotspot优化器似乎没有将Math.abs函数优化为单一指令操作。

尝试使用64位JVM或使用“-server”选项来进行基准测试。在我的机器上,abs比乘法更快。 - Holger
同上。我也进行了测试。对于我来说,abs更快。 - Mark Amabile
2个回答

5
我最初的想法是,这是由于 NaN(不是数字)的值,即如果输入为 NaN,它应该在没有任何更改的情况下返回。但是,哈罗德的测试表明,JVM 的内部优化不保留 NaN 的符号(除非您使用 StrictMath)。

Math.abs 的文档说:

换句话说,结果与以下表达式的值相同:Double.longBitsToDouble((Double.doubleToLongBits(a)<<1)>>>1)

所以,位操作的选项已知于此类的开发人员,但他们决定不使用它。
最可能的原因是,优化此 Java 代码没有意义。在大多数环境中,热点优化器会在遇到它时将其调用替换为适当的 FPU 指令。这也发生在许多 java.lang.Math 方法以及 Integer.rotateLeft 和类似方法中。它们可能有纯 Java 实现,但是如果 CPU 具有相应的指令,则 JVM 将使用该指令。

NaN是否真的需要返回未修改?毕竟,符号改变的NaN仍然是NaN。 - harold
еҘҪзҡ„пјҢжҳҫ然NaNзҡ„з¬ҰеҸ·еңЁMath.absдёӯжІЎжңүиў«дҝқз•ҷпјҢеӣ жӯӨж— жқЎд»¶ең°жё…йҷӨз¬ҰеҸ·дҪҚжҳҜе®Ңе…Ёжңүж•Ҳзҡ„гҖӮ - harold
你有一个很好的证明,即 java.lang.Math.abs 内部的 Java 代码实际上没有被执行(因为它会保留符号),而是被替换为内置函数。你可以将结果与 java.lang.StrictMath.abs 进行比较... - Holger
好的,你说得对。楼主问的是关于常规的Math.abs函数。 - harold
看起来热点优化器的表现并不是很好,因为似乎需要多次操作才能得到绝对值。在我刚刚进行的一个测试中,对于某些表达式[1]和[2],Math.abs([1]) > Math.abs([2])[1] * [1] > [2] * [2]慢。 - Tim Kuipers
@Tim Kuipers:如果您只想比较两个值的大小,可以使用乘法。但对于一般实现来说,乘法不是“abs”的替代品。因此,“abs”不必比乘法更快。它只需要比位操作的替代方案更快即可。顺便说一句,在我的机器上,需要数十亿次调用才能看到差异。 - Holger

1

我不是Java专家,但我认为问题在于这个定义在该语言中无法表示。对浮点数进行位运算是机器格式特定的,因此不可移植,因此在Java中不允许。我不确定任何JIT编译器是否会进行优化。


在Java中,格式已经很好地指定了(大多数FPUs都使用此格式)。请参见doubleToRawLongBits。 - Holger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接