当前位置:  开发笔记 > 编程语言 > 正文

当x = 0时,Java的Math.pow(x,2)表现不佳

如何解决《当x=0时,Java的Math.pow(x,2)表现不佳》经验,为你挑选了1个好方法。

背景

注意到我正在处理的java程序的执行速度比预期慢,我决定修补我认为可能导致问题的代码区域 - 从一个内部调用Math.pow(x,2) for循环.与本网站上的另一个问题相反,我创建的一个简单基准测试(最后的代码)发现用x*x替换Math.pow(x,2)实际上将循环加速了近70倍:

x*x: 5.139383ms
Math.pow(x, 2): 334.541166ms

请注意,我知道基准测试并不完美,而且值肯定应该用一点点盐 - 基准的目的是得到一个大概的数字.

问题

虽然基准测试给出了有趣的结果,但它没有准确地模拟我的数据,因为我的数据主要由0组成.因此,更准确的测试是运行基准测试,而不将for循环标记为可选.根据Math.pow()的javadoc

如果第一个参数为正零且第二个参数大于零,或者第一个参数为正无穷大且第二个参数小于零,则结果为正零.

所以预计这个基准测试运行得更快吧!?但实际上,这又慢了很多:

x*x: 4.3490535ms
Math.pow(x, 2): 3082.1720006ms

当然,人们可能期望math.pow()代码比简单的x*x代码运行慢一点,因为它需要适用于一般情况,但速度要慢700倍?到底是怎么回事!?为什么0情况比Math.random()情况慢得多?

更新: 根据@Stephen C的建议更新代码和时间.然而,这没有什么区别.

代码用于基准测试

请注意,重新排序这两个测试可以忽略不计.

public class Test {
    public Test(){
        int iterations = 100;
        double[] exampleData = new double[5000000];
        double[] test1Results = new double[iterations];
        double[] test2Results = new double[iterations];

        //Optional
        for (int i = 0; i < exampleData.length; i++) {
            exampleData[i] = Math.random();
        }

        for (int i = 0; i < iterations; i++) {
            test1Results[i] = test1(exampleData);
            test2Results[i] = test2(exampleData);
        }
        System.out.println("x*x: " + calculateAverage(test1Results) / 1000000 + "ms");
        System.out.println("Math.pow(x, 2): " + calculateAverage(test2Results) / 1000000 + "ms");
    }

    private long test1(double[] exampleData){
        double total = 0;
        long startTime;
        long endTime;
        startTime = System.nanoTime();
        for (int j = 0; j < exampleData.length; j++) {
            total += exampleData[j] * exampleData[j];
        }
        endTime = System.nanoTime();
        System.out.println(total);
        return endTime - startTime;
    }

    private long test2(double[] exampleData){
        double total = 0;
        long startTime;
        long endTime;
        startTime = System.nanoTime();
        for (int j = 0; j < exampleData.length; j++) {
            total += Math.pow(exampleData[j], 2);
        }
        endTime = System.nanoTime();
        System.out.println(total);
        return endTime - startTime;
    }

    private double calculateAverage(double[] array){
        double total = 0;
        for (int i = 0; i < array.length; i++) {
            total += array[i];
        }
        return total/array.length;
    }

    public static void main(String[] args){
        new Test();
    }
}

apangin.. 7

虽然这是一个糟糕的基准,但它幸运地揭示了一个有趣的效果.

这些数字表明您显然在"客户端"VM下运行基准测试.它没有非常强大的JIT编译器(称为C1编译器),缺乏许多优化.难怪它没有像人们预期的那样好.

Math.pow即使没有副作用,客户端VM也不够智能,无法消除呼叫.

而且,它既没有专门的快速路径也Y=2没有X=0.至少,它直到Java 9才有.最近在JDK-8063086中修复了这个问题,然后在JDK-8132207中进一步优化.

但有趣的是,使用C1编译器Math.pow确实更慢X=0!

但为什么?由于实施细节.

x86体系结构不提供计算X ^ Y的硬件指令.但还有其他有用的说明:

FYL2X 计算Y*log 2X

F2XM1 计算2 ^ X - 1

由此,X ^ Y = 2 ^(Y*log 2 X).由于log 2X仅为X> 0定义FYL2X,因此X=0以及for 返回异常-Inf.因此,它发生X=0在慢速异常路径而不是专用快速路径中.

那么该怎么办?

首先,停止使用客户端VM,特别是如果您关心性能.切换到64位版本的最新JDK 8,您将获得最佳的C2优化JIT编译器.当然,它很好地处理Math.pow(x, 2)等等.然后使用像JMH这样的适当工具编写正确的基准.



1> apangin..:

虽然这是一个糟糕的基准,但它幸运地揭示了一个有趣的效果.

这些数字表明您显然在"客户端"VM下运行基准测试.它没有非常强大的JIT编译器(称为C1编译器),缺乏许多优化.难怪它没有像人们预期的那样好.

Math.pow即使没有副作用,客户端VM也不够智能,无法消除呼叫.

而且,它既没有专门的快速路径也Y=2没有X=0.至少,它直到Java 9才有.最近在JDK-8063086中修复了这个问题,然后在JDK-8132207中进一步优化.

但有趣的是,使用C1编译器Math.pow确实更慢X=0!

但为什么?由于实施细节.

x86体系结构不提供计算X ^ Y的硬件指令.但还有其他有用的说明:

FYL2X 计算Y*log 2X

F2XM1 计算2 ^ X - 1

由此,X ^ Y = 2 ^(Y*log 2 X).由于log 2X仅为X> 0定义FYL2X,因此X=0以及for 返回异常-Inf.因此,它发生X=0在慢速异常路径而不是专用快速路径中.

那么该怎么办?

首先,停止使用客户端VM,特别是如果您关心性能.切换到64位版本的最新JDK 8,您将获得最佳的C2优化JIT编译器.当然,它很好地处理Math.pow(x, 2)等等.然后使用像JMH这样的适当工具编写正确的基准.

推荐阅读
大大炮
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有