前一段时间,我对两种不同的软件乘法算法进行了生锈的基准测试:琐碎的递归乘法和俄罗斯农民乘法。
令我惊讶的是,编译器能够分析平凡的递归,直接用结果替换对方法的调用(例如,调用mul0(4,8) -> 32
。]
为了查看JVM是否能够执行相同的优化,我通过JMH测量了以下Java实现。但是,俄罗斯的农民算法速度更快,而且看来VM并没有执行任何类似的优化。
是否在JVM中内置了类似的优化技术(将递归调用替换为预先计算的结果),或者这是JVM出于某种原因本身不执行的操作?
[我知道这取决于VM,并且可能会发生变化,所以我对阻碍VM实现者将此类优化纳入其VM的一般障碍更加感兴趣。
代码段:
@Warmup(iterations = 10)
@Fork(value = 2)
@State(Scope.Benchmark)
public class MyBenchmark {
private int F1 = 542;
private int F2 = 323;
public final static int mul0(int a, int b) {
if (a == 1) {
return b;
}
return mul0(a - 1, b) + b;
}
//O(log n)
public final static int mul2(int a, int b) {
if (a == 1) {
return b;
}
int sum = ((a & 1) == 1) ? b : 0;
return mul2(a / 2, b + b) + sum;
}
@Benchmark
public void test0() {
mul0(F1, F2);
}
@Benchmark
public void test2() {
mul2(F1, F2);
}
}
结果:
Result: 13852692,903 ▒(99.9%) 532102,125 ops/s [Average]
Statistics: (min, avg, max) = (9899651,068, 13852692,903, 15356453,576), stdev = 945811,061
Confidence interval (99.9%): [13320590,778, 14384795,028]
# Run complete. Total time: 00:02:22
Benchmark Mode Samples Score Score error Units
d.s.m.MyBenchmark.test0 thrpt 40 1453817,627 68528,256 ops/s
d.s.m.MyBenchmark.test2 thrpt 40 13852692,903 532102,125 ops/s
让我们分析优化对JVM的意义。