java - 为什么快速平方根反比在 Java 上如此奇怪和缓慢?

标签 java math sqrt math.sqrt

我正在尝试在 java 上实现 Fast Inverse Square Root 以加速 vector 规范化。但是,当我在 Java 中实现单精度版本时,一开始我获得的速度与 1F/(float)Math.sqrt() 大致相同,然后迅速下降到一半。这很有趣,因为虽然 Math.sqrt 使用(我假设)本地方法,但这涉及浮点除法,我听说这真的很慢。我计算数字的代码如下:

public static float fastInverseSquareRoot(float x){
    float xHalf = 0.5F * x;
    int temp = Float.floatToRawIntBits(x);
    temp = 0x5F3759DF - (temp >> 1);
    float newX = Float.intBitsToFloat(temp);
    newX = newX * (1.5F - xHalf * newX * newX);
    return newX;
}

使用我编写的一个短程序,每迭代 1600 万次,然后汇总结果,并重复,我得到如下结果:

1F / Math.sqrt() took 65209490 nanoseconds.
Fast Inverse Square Root took 65456128 nanoseconds.
Fast Inverse Square Root was 0.378224 percent slower than 1F / Math.sqrt()

1F / Math.sqrt() took 64131293 nanoseconds.
Fast Inverse Square Root took 26214534 nanoseconds.
Fast Inverse Square Root was 59.123647 percent faster than 1F / Math.sqrt()

1F / Math.sqrt() took 27312205 nanoseconds.
Fast Inverse Square Root took 56234714 nanoseconds.
Fast Inverse Square Root was 105.895914 percent slower than 1F / Math.sqrt()

1F / Math.sqrt() took 26493281 nanoseconds.
Fast Inverse Square Root took 56004783 nanoseconds.
Fast Inverse Square Root was 111.392402 percent slower than 1F / Math.sqrt()

我一直得到两者速度大致相同的数字,然后进行迭代,其中快速反平方根节省了 1F/Math.sqrt() 所需时间的大约 60%,随后通过几次迭代,快速反平方根运行的时间大约是控制的两倍。我很困惑为什么 FISR 会从 Same -> 快 60% -> 慢 100%,而且每次我运行我的程序时都会发生这种情况。

编辑:以上数据是我在eclipse中运行的。当我用 javac/java 运行程序时,我得到了完全不同的数据:

1F / Math.sqrt() took 57870498 nanoseconds.
Fast Inverse Square Root took 88206794 nanoseconds.
Fast Inverse Square Root was 52.421004 percent slower than 1F / Math.sqrt()

1F / Math.sqrt() took 54982400 nanoseconds.
Fast Inverse Square Root took 83777562 nanoseconds.
Fast Inverse Square Root was 52.371599 percent slower than 1F / Math.sqrt()

1F / Math.sqrt() took 21115822 nanoseconds.
Fast Inverse Square Root took 76705152 nanoseconds.
Fast Inverse Square Root was 263.259133 percent slower than 1F / Math.sqrt()

1F / Math.sqrt() took 20159210 nanoseconds.
Fast Inverse Square Root took 80745616 nanoseconds.
Fast Inverse Square Root was 300.539585 percent slower than 1F / Math.sqrt()

1F / Math.sqrt() took 21814675 nanoseconds.
Fast Inverse Square Root took 85261648 nanoseconds.
Fast Inverse Square Root was 290.845374 percent slower than 1F / Math.sqrt()

EDIT2: 几次响应后,速度似乎在几次迭代后稳定下来,但它稳定到的数字非常不稳定。有人知道为什么吗?

这是我的代码(不是很简洁,但这是全部):

public class FastInverseSquareRootTest {

    public static FastInverseSquareRootTest conductTest() {
        float result = 0F;
        long startTime, endTime, midTime;
        startTime = System.nanoTime();
        for (float x = 1F; x < 4_000_000F; x += 0.25F) {
            result = 1F / (float) Math.sqrt(x);
        }
        midTime = System.nanoTime();
        for (float x = 1F; x < 4_000_000F; x += 0.25F) {
            result = fastInverseSquareRoot(x);
        }
        endTime = System.nanoTime();
        return new FastInverseSquareRootTest(midTime - startTime, endTime
                - midTime);
    }

    public static float fastInverseSquareRoot(float x) {
        float xHalf = 0.5F * x;
        int temp = Float.floatToRawIntBits(x);
        temp = 0x5F3759DF - (temp >> 1);
        float newX = Float.intBitsToFloat(temp);
        newX = newX * (1.5F - xHalf * newX * newX);
        return newX;
    }

    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 7; i++) {
            System.out.println(conductTest().toString());
        }
    }

    private long controlDiff;

    private long experimentalDiff;

    private double percentError;

    public FastInverseSquareRootTest(long controlDiff, long experimentalDiff) {
        this.experimentalDiff = experimentalDiff;
        this.controlDiff = controlDiff;
        this.percentError = 100D * (experimentalDiff - controlDiff)
                / controlDiff;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(String.format("1F / Math.sqrt() took %d nanoseconds.%n",
                controlDiff));
        sb.append(String.format(
                "Fast Inverse Square Root took %d nanoseconds.%n",
                experimentalDiff));
        sb.append(String
                .format("Fast Inverse Square Root was %f percent %s than 1F / Math.sqrt()%n",
                        Math.abs(percentError), percentError > 0D ? "slower"
                                : "faster"));
        return sb.toString();
    }
}

最佳答案

JIT 优化器似乎已经放弃了对 Math.sqrt 的调用。

用你未修改的代码,我得到了

1F / Math.sqrt() took 65358495 nanoseconds.
Fast Inverse Square Root took 77152791 nanoseconds.
Fast Inverse Square Root was 18,045544 percent slower than 1F / Math.sqrt()

1F / Math.sqrt() took 52872498 nanoseconds.
Fast Inverse Square Root took 75242075 nanoseconds.
Fast Inverse Square Root was 42,308531 percent slower than 1F / Math.sqrt()

1F / Math.sqrt() took 23386359 nanoseconds.
Fast Inverse Square Root took 73532080 nanoseconds.
Fast Inverse Square Root was 214,422951 percent slower than 1F / Math.sqrt()

1F / Math.sqrt() took 23790209 nanoseconds.
Fast Inverse Square Root took 76254902 nanoseconds.
Fast Inverse Square Root was 220,530610 percent slower than 1F / Math.sqrt()

1F / Math.sqrt() took 23885467 nanoseconds.
Fast Inverse Square Root took 74869636 nanoseconds.
Fast Inverse Square Root was 213,452678 percent slower than 1F / Math.sqrt()

1F / Math.sqrt() took 23473514 nanoseconds.
Fast Inverse Square Root took 73063699 nanoseconds.
Fast Inverse Square Root was 211,260168 percent slower than 1F / Math.sqrt()

1F / Math.sqrt() took 23738564 nanoseconds.
Fast Inverse Square Root took 71917013 nanoseconds.
Fast Inverse Square Root was 202,954353 percent slower than 1F / Math.sqrt()

fastInverseSquareRoot 的运行时间一直较慢,并且其运行时间都在同一个球场,而 Math.sqrt 调用速度大大加快。

更改代码以便无法避免对 Math.sqrt 的调用,

    for (float x = 1F; x < 4_000_000F; x += 0.25F) {
        result += 1F / (float) Math.sqrt(x);
    }
    midTime = System.nanoTime();
    for (float x = 1F; x < 4_000_000F; x += 0.25F) {
        result -= fastInverseSquareRoot(x);
    }
    endTime = System.nanoTime();
    if (result == 0) System.out.println("Wow!");

我得到了

1F / Math.sqrt() took 184884684 nanoseconds.
Fast Inverse Square Root took 85298761 nanoseconds.
Fast Inverse Square Root was 53,863804 percent faster than 1F / Math.sqrt()

1F / Math.sqrt() took 182183542 nanoseconds.
Fast Inverse Square Root took 83040574 nanoseconds.
Fast Inverse Square Root was 54,419278 percent faster than 1F / Math.sqrt()

1F / Math.sqrt() took 165269658 nanoseconds.
Fast Inverse Square Root took 81922280 nanoseconds.
Fast Inverse Square Root was 50,431143 percent faster than 1F / Math.sqrt()

1F / Math.sqrt() took 163272877 nanoseconds.
Fast Inverse Square Root took 81906141 nanoseconds.
Fast Inverse Square Root was 49,834815 percent faster than 1F / Math.sqrt()

1F / Math.sqrt() took 165314846 nanoseconds.
Fast Inverse Square Root took 81124465 nanoseconds.
Fast Inverse Square Root was 50,927296 percent faster than 1F / Math.sqrt()

1F / Math.sqrt() took 164079534 nanoseconds.
Fast Inverse Square Root took 80453629 nanoseconds.
Fast Inverse Square Root was 50,966689 percent faster than 1F / Math.sqrt()

1F / Math.sqrt() took 162350821 nanoseconds.
Fast Inverse Square Root took 79854355 nanoseconds.
Fast Inverse Square Root was 50,813704 percent faster than 1F / Math.sqrt()

很多 Math.sqrt 的时间慢了很多,而 fastInverseSqrt 的时间只稍微慢了一点(现在它必须在每次迭代中做一个减法).

关于java - 为什么快速平方根反比在 Java 上如此奇怪和缓慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16551140/

相关文章:

java - 如何使用 Sikuli + Java 根据相邻文本选择单选按钮?

c# - 如何在 C# 中实现绘图器

c# - 将无穷大数学行为添加到 Decimal

java - BigInteger Sqrt 函数不收敛

c - sqrt 仅在参数为非负时定义

java - 具有数据驱动开发的最佳 Java BDD 框架?

java - 在 spring boot 中添加外部静态文件(css、js、png ...)

java - 使用 Jersey 解析子节点

math - 用于跟踪最坏情况错误的算术库

python - 如何有效地将整数提升为小数幂?