首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么这段使用流的代码在Java 9中的运行速度比Java 8快得多?

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

Stack Overflow用户
提问于 2017-10-07 00:18:31
回答 1查看 1.6K关注 0票数 6

我在解决问题205 of Euler项目时发现了这个问题。问题如下:

彼得有九个四面(金字塔)骰子,每个骰子都有编号为1,2,3,4的面。科林有六面(立方)骰子,每个骰子的号码是1,2,3,4,5,6。 彼得和科林掷骰子比较总数:最高的总胜利。如果总数相等,则结果是平局。 金字塔皮特打败立方科林的概率是多少?给你的答案四舍五入到小数点后的7位,表格0. 0.abcdefg

我用番石榴编写了一个简单的解决方案:

代码语言:javascript
复制
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(),在Java9中运行速度似乎比Java 8快得多。具体来说,Java8在我的机器上计算的是37465 my的解决方案。Java 9是在大约16000 is内完成的,无论我运行的是用Java 8编译的文件还是用Java 9编译的文件。

如果我将streams代码替换为似乎与流前等效的代码:

代码语言:javascript
复制
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之间没有可测量的差别。

我在这里错过了什么?为什么流代码在Java9中要快得多,为什么for循环不是呢?

我正在运行Ubuntu16.04LTS 64位.我的Java 8版本:

代码语言:javascript
复制
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版本:

代码语言:javascript
复制
java version "9"
Java(TM) SE Runtime Environment (build 9+181)
Java HotSpot(TM) 64-Bit Server VM (build 9+181, mixed mode)
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-10-09 00:44:12

1.为什么流在JDK 9上运行得更快?

Stream.count()实现是JDK 8中的相当愚蠢:它只是遍历整个流,为每个元素添加1L

这是JDK 9中的固定,尽管bug报告提到了大小流,但是新代码也改进了非大小流。

如果您用Java8风格的实现.count()替换.mapToLong(e -> 1L).sum(),那么即使在JDK 9上,它也会再次慢下来。

2.为什么朴素循环工作慢?

当您将所有代码放入main方法中时,它无法高效编译。此方法只执行一次,在解释器中开始运行,然后,当JVM检测到热循环时,它将从解释模式切换到正在编译的运行模式。这被称为栈上替换(OSR).

OSR编译通常没有常规编译方法那么优化。我在前面已经详细解释了这一点,请参阅的答案。

如果将内部循环放在单独的方法中,JIT将产生更好的代码:

代码语言:javascript
复制
    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上的流。

票数 20
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/46615484

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档