为什么使用流的此代码在Java 9中的运行速度比Java 8快得多?
我发现这一点的同时解决问题205的项目欧拉。问题如下:
彼得有九个四面(金字塔形)骰子,每个骰子的编号分别为1、2、3、4。科林有六个六边形(立方)骰子,每个骰子的编号为1、2、3、4、5、6。
彼得和科林掷骰子并比较总数:最高的总胜利数。如果总数相等,则结果为平局。
金字塔形皮特击败立方柯林的概率是多少?以0.abcdefg的形式将答案四舍五入到小数点后七个位
我用番石榴写了一个幼稚的解决方案:
import com.google.common.collect.Sets;import com.google.common.collect.ImmutableSet;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;
public class Problem205 {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
List<Integer> peter = Sets.cartesianProduct(Collections.nCopies(9, ImmutableSet.of(1, 2, 3, 4)))
.stream()
.map(l -> l
.stream()
.mapToInt(Integer::intValue)
.sum())
.collect(Collectors.toList());
List<Integer> colin = Sets.cartesianProduct(Collections.nCopies(6, ImmutableSet.of(1, 2, 3, 4, 5, 6)))
.stream()
.map(l -> l
.stream()
.mapToInt(Integer::intValue)
.sum())
.collect(Collectors.toList());
long startTime2 = System.currentTimeMillis();
// IMPORTANT BIT HERE! v
long solutions = peter
.stream()
.mapToLong(p -> colin
.stream()
.filter(c -> p > c)
.count())
.sum();
// IMPORTANT BIT HERE! ^
System.out.println("Counting solutions took " + (System.currentTimeMillis() - startTime2) + "ms");
System.out.println("Solution: " + BigDecimal
.valueOf(solutions)
.divide(BigDecimal
.valueOf((long) Math.pow(4, 9) * (long) Math.pow(6, 6)),
7,
RoundingMode.HALF_UP));
System.out.println("Found in: " + (System.currentTimeMillis() - startTime) + "ms");
}
}
我强调的代码,它使用简单filter()
,count()
而且sum()
,似乎比Java 8。具体在Java
9跑得更快,Java的8计数在我的机器上37465ms的解决方案。Java 9大约在16000毫秒内完成了此操作,无论我运行的是用Java
8编译的文件还是运行用Java 9编译的文件,这都是相同的。
如果我将流代码替换为似乎完全等同于预流的代码:
long solutions = 0;for (Integer p : peter) {
long count = 0;
for (Integer c : colin) {
if (p > c) {
count++;
}
}
solutions += count;
}
它计算解决方案的时间约为35000毫秒,Java 8和Java 9之间没有可测量的差异。
我在这里想念什么?为什么Java 9中的流代码如此之快,为什么不for
循环呢?
我正在运行64位Ubuntu 16.04 LTS。我的Java 8版本:
java version "1.8.0_131"Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)
我的Java 9版本:
java version "9"Java(TM) SE Runtime Environment (build 9+181)
Java HotSpot(TM) 64-Bit Server VM (build 9+181, mixed mode)
回答:
1.为什么流在JDK 9上运行更快
Stream.count()
在JDK
8中,实现相当笨拙:它只是迭代整个流,1L
为每个元素添加。
这已在JDK 9
中修复。即使错误报告中提到了SIZED流,新代码也可以改善非大小型流。
如果您替换.count()
为Java 8样式的实现.mapToLong(e -> 1L).sum()
,即使在JDK 9上,它也会再次变慢。
2.为什么天真的循环工作缓慢
当您将所有代码放入main
方法中时,就无法有效地对其进行JIT编译。此方法仅执行一次,它开始在解释器中运行,随后,当JVM检测到热循环时,它会从解释模式切换为即时编译。这称为堆栈上替换(OSR)。
OSR编译通常没有常规编译方法那样优化。我详细解释这个前面,看到这个和这个答案。
如果将内部循环放在单独的方法中,JIT将产生更好的代码:
long solutions = 0; for (Integer p : peter) {
solutions += countLargerThan(colin, p);
}
...
private static int countLargerThan(List<Integer> colin, int p) {
int count = 0;
for (Integer c : colin) {
if (p > c) {
count++;
}
}
return count;
}
在这种情况下,countLargerThan
方法将被正常编译,并且其性能将优于在JDK 8和JDK 9上使用流。
以上是 为什么使用流的此代码在Java 9中的运行速度比Java 8快得多? 的全部内容, 来源链接: utcz.com/qa/401939.html