为什么Math.pow(int,int)比我的朴素实现慢?

时间:2016-01-06 16:40:27

标签: java algorithm performance

昨天我看到一个问题,问Math.pow(int,int)为什么这么慢,但问题措辞不多,没有展示研究工作,所以很快就关闭了。

我对自己进行了一些测试,发现Math.pow方法在处理整数参数时与我自己的朴素实现(这甚至不是一个特别有效的实现)相比实际上运行得非常慢。下面是我为测试它而运行的代码:

class PowerTest {

    public static double myPow(int base, int exponent) {
        if(base == 0) return 0;
        if(exponent == 0) return 1;
        int absExponent = (exponent < 0)? exponent * -1 : exponent;
        double result = base;
        for(int i = 1; i < absExponent; i++) {
            result *= base;
        }
        if(exponent < 1) result = 1 / result;
        return result;
    }

    public static void main(String args[]) {
        long startTime, endTime;

        startTime = System.nanoTime();
        for(int i = 0; i < 5000000; i++) {
            Math.pow(2,2);
        }
        endTime = System.nanoTime();
        System.out.printf("Math.pow took %d milliseconds.\n", (endTime - startTime) / 1000000);

        startTime = System.nanoTime();
        for(int i = 0; i < 5000000; i++) {
            myPow(2,2);
        }
        endTime = System.nanoTime();
        System.out.printf("myPow took %d milliseconds.\n", (endTime - startTime) / 1000000);
    }

}

在我的计算机上(英特尔x86_64 cpu上的linux),输出几乎总是报告Math.pow耗时10毫秒而myPow耗时2毫秒。这偶尔会在这里或那里波动一毫秒,但Math.pow的平均运行速度慢5倍

我做了一些研究,根据grepcodeMath.pow只提供了类型签名为(double, double)的方法,并将其推迟到StrictMath.pow方法,即本机方法调用。

Math库仅提供处理双打的pow函数这一事实似乎表明该问题的可能的答案。显然,必须处理double类型的基数或指数的可能性的幂算法将比我的仅处理整数的算法花费更长的时间来执行。但是,最后,它归结为依赖于体系结构的本机代码(它几乎总是比JVM字节代码运行得更快,在我的情况下可能是C或汇编)。 在这个级别,似乎会进行优化以检查数据类型并尽可能运行更简单的算法。

鉴于此信息,为什么在给定整数参数时,本机Math.pow方法的运行速度比未优化且天真的myPow方法慢得多?

4 个答案:

答案 0 :(得分:8)

正如其他人所说,你不能忽略double的使用,因为浮点运算几乎肯定会慢一些。但是,这不是唯一的原因 - 如果您更改实施以使用它们,它仍然会更快。

这是因为有两件事:首先是2^2(指数,而不是xor)是一个非常快速的计算,所以你的算法可以很好地用于此 - 尝试使用来自{{的两个值1}}(或Random#nextInt)你会发现nextDouble实际上要快得多。

另一个原因是调用本机方法有开销,这在这里实际上是有意义的,因为Math#pow计算起来很快,而且你多次调用2^2。有关详情,请参阅What makes JNI calls slow?

答案 1 :(得分:1)

没有pow(int,int)功能。您正在将苹果与橙子进行比较,并简化了浮点数可以忽略的假设。

答案 2 :(得分:0)

Math.pow很慢,因为它处理一般意义上的方程式,使用分数幂将其提升到给定的幂。它是计算花费更多时间时必须经历的查找。

简单地将数字相乘通常会更快,因为Java中的本机调用效率更高。

编辑:值得注意的是,数学函数使用双精度数,与使用整数相比,这也可能需要更长的时间。

答案 3 :(得分:0)

Math.pow(x, y) 可能已实施为exp(y log x)。这允许分数指数并且非常快。

但如果您只需要小的积分参数,那么您可以通过编写自己的版本来打败这种性能。

可以说Java可以为你做检查,但对于大整数来说,内置版本会更快。它还必须定义一个适当的积分返回类型,并且溢出的风险是显而易见的。定义分支区域周围的行为会很棘手。

顺便说一句,你的整数版本可能会更快。通过平方对取幂进行一些研究。