&引用;“快速”;Java中的整数幂
[简短回答:糟糕的基准测试方法。你可能认为我现在已经明白了这一点。] 问题是“找到一种快速计算x^y的方法,其中x和y是正整数”。典型的“快速”算法如下所示:&引用;“快速”;Java中的整数幂,java,algorithm,performance,Java,Algorithm,Performance,[简短回答:糟糕的基准测试方法。你可能认为我现在已经明白了这一点。] 问题是“找到一种快速计算x^y的方法,其中x和y是正整数”。典型的“快速”算法如下所示: public long fastPower(int x, int y) { // Replaced my code with the "better" version described below, // but this version isn't measurably faster than what I had befor
public long fastPower(int x, int y) {
// Replaced my code with the "better" version described below,
// but this version isn't measurably faster than what I had before
long base = x; // otherwise, we may overflow at x *= x.
long result = y % 2 == 1 ? x : 1;
while (y > 1) {
base *= base;
y >>= 1;
if (y % 2 == 1) result *= base;
}
return result;
}
public long naivePower(int x, int y) {
long result = 1;
for (int i = 0; i < y; i++) {
result *= x;
}
return result;
}
我想看看这比调用Math.pow()或使用简单的方法(如x乘以y)快多少,比如:
public long fastPower(int x, int y) {
// Replaced my code with the "better" version described below,
// but this version isn't measurably faster than what I had before
long base = x; // otherwise, we may overflow at x *= x.
long result = y % 2 == 1 ? x : 1;
while (y > 1) {
base *= base;
y >>= 1;
if (y % 2 == 1) result *= base;
}
return result;
}
public long naivePower(int x, int y) {
long result = 1;
for (int i = 0; i < y; i++) {
result *= x;
}
return result;
}
public long-power(int x,int y){
长期结果=1;
for(int i=0;i
编辑:好的,有人(正确地)向我指出,我的基准测试代码并没有使用结果,这完全把一切都抛在脑后了。一旦我开始使用结果,我仍然看到天真的方法比“快速”方法快25%左右
原文:
我非常惊讶地发现,Naiver方法比“fast”版本快4倍,而“fast”版本本身比Math.pow()版本快3倍。
我的测试使用10000000次(然后是1亿次,只是为了绝对确保JIT有时间预热),每次使用随机值(防止调用被优化)2while循环运行
log2(y)
次,而for循环运行y
次,因此根据您的输入,一个跑得比另一个快
while循环在最坏情况下运行:
while
conditional)循环运行:
比较(用于
条件)
乘法运算,以及
增量(
迭代器的)
因此,对于y
的较小值,您希望naive循环更快,因为for
循环中较少的操作数优于“快速”方法的log2缩减,只要这些额外操作所损失的时间大于log2缩减y所获得的时间 如果你无法达到你的基准,那么尝试细分你的结果就没有什么意义了。它们可能是由于输入选择不当、错误的基准测试实践(例如在一个测试之前运行另一个测试(从而给JVM“预热”时间)等等。请分享您的基准代码,而不仅仅是您的结果
我建议在您的测试中包括Guava的(),这是一种大量使用且经过良好基准测试的方法。虽然您可能能够通过某些输入击败它,但在一般情况下,您不太可能改进它的运行时(如果可以,他们很乐意听到)
毫不奇怪,Math.pow()
比纯正整数算法的性能更差。看看“快速”与“幼稚”的实现,很明显,这在很大程度上取决于您选择的输入,正如Mike'Pomax'Kamermans所建议的那样。对于y
的小值,“幼稚”解决方案显然需要做更少的工作。但是对于更大的值,我们通过“快速”实现节省了大量的迭代次数。在我看来,问题中的第一个fastPower(base,exponent)
如果没有给出错误的结果,那就是错误的。(下面的intPower()
的第一个版本是buggy,除了有点误导性的基准测试结果外,还给出了错误的结果。)
由于评论“格式化功能”,另一个指数化的表现形式是通过平方来争论作为答案:
static public long intPower(int base, int exponent) {
if (0 == base
|| 1 == base)
return base;
int y = exponent;
if (y <= 0)
return 0 == y ? 1 : -1 != base ? 0 : y % 2 == 1 ? -1 : 1;
long result = y % 2 == 1 ? base : 1,
power = base;
while (1 < y) {
power *= power;
y >>= 1; // easier to see termination after Type.SIZE iterations
if (y % 2 == 1)
result *= power;
}
return result;
}
您的快速电源有两个问题:
最好将y%2==0
替换为(y&1)==0
;按位运算速度更快
您的代码总是递减y
并执行额外的乘法,包括y
为偶数的情况。最好把这部分放在else
子句中
无论如何,我猜你的基准测试方法并不完美。4倍的性能差异听起来很奇怪,如果没有完整的代码就无法解释
应用上述改进后,我使用基准测试验证了fastPower
确实比naivePower
快,系数为1.3x到2x
package bench;
import org.openjdk.jmh.annotations.*;
@State(Scope.Benchmark)
public class FastPow {
@Param("3")
int x;
@Param({"25", "28", "31", "32"})
int y;
@Benchmark
public long fast() {
return fastPower(x, y);
}
@Benchmark
public long naive() {
return naivePower(x, y);
}
public static long fastPower(long x, int y) {
long result = 1;
while (y > 0) {
if ((y & 1) == 0) {
x *= x;
y >>>= 1;
} else {
result *= x;
y--;
}
}
return result;
}
public static long naivePower(long x, int y) {
long result = 1;
for (int i = 0; i < y; i++) {
result *= x;
}
return result;
}
}
注意:整数乘法运算速度非常快。不要期望通过适合long
的值来获得巨大的性能改进。快速幂算法的优势将在指数较大的biginger
上体现出来
更新
由于作者发布了基准测试,我必须承认,令人惊讶的性能结果来自常见的基准测试陷阱。
我在保留原始方法的同时改进了基准测试,现在它表明FastPower
确实比NaivePower
快
改进版本中的关键更改是什么
应该在不同的JVM实例中分别测试不同的算法,以防止配置文件污染
必须多次调用基准以允许正确编译/重新编译,直到达到稳定状态
一个基准测试应该放在一个单独的方法中,以避免堆栈替换问题
y%2
被替换为y&1
,因为HotSpot不会自动执行此优化
最小化主基准循环中不相关操作的影响
手工编写微基准是一项困难的任务。这就是为什么强烈建议使用适当的基准测试框架,如。确定if(odd)
控制哪些操作?快速求幂算法对于机器整数算法没有多大价值。只有当一次乘法的成本明显大于任何一次乘法的减量时
package bench;
import org.openjdk.jmh.annotations.*;
@State(Scope.Benchmark)
public class FastPow {
@Param("3")
int x;
@Param({"25", "28", "31", "32"})
int y;
@Benchmark
public long fast() {
return fastPower(x, y);
}
@Benchmark
public long naive() {
return naivePower(x, y);
}
public static long fastPower(long x, int y) {
long result = 1;
while (y > 0) {
if ((y & 1) == 0) {
x *= x;
y >>>= 1;
} else {
result *= x;
y--;
}
}
return result;
}
public static long naivePower(long x, int y) {
long result = 1;
for (int i = 0; i < y; i++) {
result *= x;
}
return result;
}
}
Benchmark (x) (y) Mode Cnt Score Error Units
FastPow.fast 3 25 thrpt 10 103,406 ± 0,664 ops/us
FastPow.fast 3 28 thrpt 10 103,520 ± 0,351 ops/us
FastPow.fast 3 31 thrpt 10 85,390 ± 0,286 ops/us
FastPow.fast 3 32 thrpt 10 115,868 ± 0,294 ops/us
FastPow.naive 3 25 thrpt 10 76,331 ± 0,660 ops/us
FastPow.naive 3 28 thrpt 10 69,527 ± 0,464 ops/us
FastPow.naive 3 31 thrpt 10 54,407 ± 0,231 ops/us
FastPow.naive 3 32 thrpt 10 56,127 ± 0,207 ops/us