java - 为什么这段使用流的代码在 Java 9 中比在 Java 8 中运行得快得多?

标签 java performance java-8 java-stream java-9

我在解决 Problem 205 时发现了这个的 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

我使用 Guava 编写了一个简单的解决方案:

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 循环不是?


我正在运行 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)

我的 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() 实现是 rather dumb在 JDK 8 中:它只是遍历整个流,为每个元素添加 1L

这是 fixed在 JDK 9 中。尽管错误报告提到了 SIZED 流,但新代码也改进了非大小流。

如果将 .count() 替换为 Java 8 风格的实现 .mapToLong(e -> 1L).sum(),即使在JDK 9.

2。为什么朴素循环运行缓慢

当您将所有代码放在 main 方法中时,它无法有效地进行 JIT 编译。此方法只执行一次,它开始在解释器中运行,稍后,当 JVM 检测到热循环时,它会从解释模式切换到即时编译模式。这称为堆栈替换 (OSR)。

OSR 编译通常不如常规编译方法优化。我之前已经对此进行了详细解释,请参阅 thisthis回答。

如果将内部循环放在单独的方法中,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 - 为什么这段使用流的代码在 Java 9 中比在 Java 8 中运行得快得多?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46615484/

相关文章:

hadoop - 构建不适合内存的流

java - List<String> 类型中的方法 get(int) 不适用于 Java 8 中的参数字符串

javascript - 计算从 1 到 k 的 lcm(i, j)、i 和 j 之和的最有效方法是什么?

performance - DNS 预取和页面优化

java - CompletionStage.thenCompose 不连续执行

java - JSP 需要重新编译吗?

java - 如何自动添加 XML 注释

java - Java中ICMP请求需要获取哪些权限?

java - 将第二个json对象添加到java中的同一个json数组中

performance - 内存管理的90/10规则?