Java中int型如何求幂_关于java:为什么Math.pow(int,int)慢于我的幼稚实现?

昨天我看到一个问题,问为什么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.

", (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.

", (endTime - startTime) / 1000000);

}

}

在我的计算机(intel x86_64 cpu上的Linux)上,输出几乎总是报告Math.pow花了10ms,而myPow花了2ms。这有时在这里或那里波动了毫秒,但Math.pow平均慢了大约5倍。

我做了一些研究,根据grepcode的说法,Math.pow仅提供了一种具有(double, double)类型签名的方法,并且将其推迟到StrictMath.pow方法中,该方法是本机方法调用。

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

有了这些信息,当给定整数参数时,为什么本机Math.pow方法始终比我未优化且天真的myPow方法运行慢得多?

我对此发表评论:int absExponent =(指数<0)? 指数* -1:指数; 您不需要*:int absExponent =(指数<0)? -指数:指数;

那甚至不是最佳的(整数)幂算法-一种高效的算法可以执行if (absExponent & 1 == 0) { result *= result; absExponent >>= 1 }-即它以O(log n)而不是O(n)时间运行。

由于您的实现在指数上使用循环,因此计算平方是一个非常有偏差的基准。 您为什么不对2都尝试呢? :)(当然,使用较简单的算法可能会做得更好,但是即使那样,2?也会显示不同的结果。)

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

这是由于两件事:首先是2^2(指数,而不是xor)是执行起来非常快的计算,因此您的算法很好用-尝试使用Random#nextInt(或),您会发现Math#pow实际上要快得多。

另一个原因是调用本机方法会产生开销,这在这里实际上是有意义的,因为2^2的计算速度如此之快,而您调用Math#pow的次数却很多。请参阅是什么使JNI调用变慢?有关此的更多信息。

啊哈!我将所有参数替换为对nextInt(1000)的调用,现在Math.pow方法的运行速度比我的实现快得多。因此事实证明这是一个不好的测试案例。

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

但它传递给了本机代码。确定优化将在本机级别进行吗?

@WoodrowBarlow传递给本机代码的值为double。您是否建议如果本机代码未检测到小数部分,应使用替代路径?将参数转换为整数类型并使用整数运算符进行运算的路径?

好吧,当然。有什么理由会导致一个坏主意吗?如果不是一个好主意,为什么不使用整数类型的变量重载pow方法呢?对我来说,这似乎是一个高价值的优化,但是我知道可能缺少一些东西。

每次调用pow()时,检查和分支将花费额外的时间,并且整数参数的情况是不典型的。提高功率时的测试性能,您会发现测试并不像"有分数部分吗?"那样简单。因此,您放慢了典型的用例,却收效甚微。重载pow()函数可以避免此问题,但是当它如此容易实现自己时,用这样一个专门的函数将API弄乱是不值得的。

我懂了。谢谢。

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

但是,如果只需要小的正整数参数,则可以通过编写自己的版本来击败这种性能。

可以说Java可以帮您解决这个问题,但是对于大型整数,内置版本会更快。还必须定义适当的整数返回类型,并且明显存在溢出的风险。定义分支区域周围的行为将非常棘手。

顺便说一下,您的整数类型版本可能会更快。对平方求幂进行一些研究。

Math.pow之所以缓慢,是因为它处理一般意义上的方程,使用小数幂将其提高到给定的幂。计算需要更多时间,因此必须经过查找。

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

编辑:值得一提的是,数学函数使用双精度,这比使用整数要花费更长的时间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值