为什么使用流的代码在Java 9中的运行速度比Java 8快得多? [英] Why does this code using streams run so much faster in Java 9 than Java 8?

查看:279
本文介绍了为什么使用流的代码在Java 9中的运行速度比Java 8快得多?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在解决问题205 时发现了这一点。 net /rel =nofollow noreferrer>项目Euler 。问题如下:

I discovered this while solving Problem 205 of Project Euler. The problem is as follows:


彼得有九个四面(金字塔)骰子,每个骰子都有编号为1,2,3,4的面孔。
科林有六个六面(立方体)骰子,每个骰子都有编号为1,2,3,4,5,6的面孔。

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.

金字塔皮特击败立方科林的几率是多少?将你的答案四舍五入到小数点后七位0.abcdefg

What is the probability that Pyramidal Pete beats Cubic Colin? Give your answer rounded to seven decimal places in the form 0.abcdefg

我用Guava写了一个天真的解决方案:

I wrote a naive solution using 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在我的机器上计算37465ms的解决方案。 Java 9大约需要16000毫秒,无论是运行使用Java 8编译的文件还是使用Java 9编译的文件都是一样的。

The code I have highlighted, which uses a simple filter(), count() and sum(), seems to run much faster in Java 9 than Java 8. Specifically, Java 8 counts the solutions in 37465ms on my machine. Java 9 does it in about 16000ms, which is the same whether I run the file compiled with Java 8 or one compiled with Java 9.

如果我用以下代码替换流代码什么似乎是确切的前流等价物:

If I replace the streams code with what would seem to be the exact pre-streams equivalent:

long solutions = 0;
for (Integer p : peter) {
    long count = 0;
    for (Integer c : colin) {
        if (p > c) {
            count++;
        }
    }
    solutions += count;
}

大约35000毫秒的解决方案,Java 8和Java 8之间没有可衡量的差异Java 9。

It counts the solutions in about 35000ms, with no measurable difference between Java 8 and Java 9.

我在这里缺少什么?为什么Java 9中的代码编码速度要快得多,为什么不是 for 循环?

What am I missing here? Why is the streams code so much faster in Java 9, and why isn't the for loop?

我正在运行Ubuntu 16.04 LTS 64位。我的Java 8版本:

I am running Ubuntu 16.04 LTS 64-bit. My Java 8 version:

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版本:

My Java 9 version:

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

1. Why the stream works faster on JDK 9

Stream.count() implementation is rather dumb in JDK 8: it just iterates through the whole stream adding 1L for each element.

这是已修复。尽管错误报告中提到了SIZED流,但新代码改进了非大小的流也是。

This was fixed in JDK 9. Even though the bug report says about SIZED streams, new code improves non-sized streams, too.

如果用Java 8风格的实现替换 .count() .mapToLong(e - > 1L).sum(),即使在JDK 9上它也会再慢。

If you replace .count() with Java 8-style implementation .mapToLong(e -> 1L).sum(), it will be slow again even on JDK 9.

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

When you put all your code in main method, it cannot be JIT-compiled efficiently. This method is executed only once, it starts running in interpreter and later, when JVM detects a hot loop, it switches from interpreted mode to compiled on-the-go. This is called on-stack replacement (OSR).

OSR编译通常不像常规编译方法那样优化。我之前已详细解释过这个问题,请参阅这个答案。

OSR compilations are often not as optimized as regular compiled methods. I've explained this in detail earlier, see this and this answer.

如果你把内部循环放在一个单独的方法中,JIT会产生更好的代码:

JIT will produce better code if you put the inner loop in a separate method:

    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上的流。

In this case countLargerThan method will be compiled normally, and the performance will be better than with streams both on JDK 8 and on JDK 9.

这篇关于为什么使用流的代码在Java 9中的运行速度比Java 8快得多?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆