为什么此代码在 Java 9 中使用流 运行 比 Java 8 快得多?
Why does this code using streams run so much faster in Java 9 than Java 8?
我在解决Problem 205 of Project Euler时发现了这个。问题如下:
Peter has nine four-sided (pyramidal) dice, each with faces numbered 1, 2, 3, 4.
Colin has six six-sided (cubic) dice, each with faces numbered 1, 2, 3, 4, 5, 6.
Peter and Colin roll their dice and compare totals: the highest total wins. The result is a draw if the totals are equal.
What is the probability that Pyramidal Pete beats Cubic Colin? Give your answer rounded to seven decimal places in the form 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 9 中快得多Java 8. 具体来说,Java 8 在我的机器上计算 37465 毫秒内的解决方案。 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
循环不是?
我是 运行ning Ubuntu 16.04 LTS 64 位。我的 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)
我的Java9版本:
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()
实现是 rather dumb in JDK 8:它只是遍历整个流,为每个元素添加 1L
。
这是 JDK 9 中的 fixed。即使错误报告提到了 SIZED 流,新代码也改进了非大小流。
如果将 .count()
替换为 Java 8 样式实现 .mapToLong(e -> 1L).sum()
,即使在 JDK 9.
上也会再次变慢
2。为什么天真的循环运行缓慢
当您将所有代码都放在 main
方法中时,无法有效地进行 JIT 编译。这个方法只执行一次,它在解释器中启动 运行 之后,当 JVM 检测到热循环时,它会从解释模式切换到运行中的编译模式。这称为堆栈替换 (OSR)。
OSR 编译通常不如常规编译方法优化。我之前已经详细解释过了,请参阅 and 回答。
如果将内部循环放在单独的方法中,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 上的流。
我在解决Problem 205 of Project Euler时发现了这个。问题如下:
Peter has nine four-sided (pyramidal) dice, each with faces numbered 1, 2, 3, 4. Colin has six six-sided (cubic) dice, each with faces numbered 1, 2, 3, 4, 5, 6.
Peter and Colin roll their dice and compare totals: the highest total wins. The result is a draw if the totals are equal.
What is the probability that Pyramidal Pete beats Cubic Colin? Give your answer rounded to seven decimal places in the form 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 9 中快得多Java 8. 具体来说,Java 8 在我的机器上计算 37465 毫秒内的解决方案。 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
循环不是?
我是 运行ning Ubuntu 16.04 LTS 64 位。我的 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)
我的Java9版本:
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()
实现是 rather dumb in JDK 8:它只是遍历整个流,为每个元素添加 1L
。
这是 JDK 9 中的 fixed。即使错误报告提到了 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 上的流。