【问题标题】:Java 8 Parallel processing and Lambda expressionJava 8 并行处理和 Lambda 表达式
【发布时间】:2014-03-28 03:48:03
【问题描述】:

我想测试 Java8 并行流的速度有多快,所以我编写了一个程序。该程序计算给定数字列表中素数的数量。该程序以这些方式计算素数:

  1. 使用 for 循环;
  2. 通过使用 lambda 表达式;
  3. 通过使用 lambda 表达式(并行流)。

在执行程序之前,我期望并行流版本应该更快。但结果是

在 4237 英里秒内找不到总质数 664579 ----for 循环版本
在 2440 英里秒内找不到总素数 664579 ----并行流
在 2166 英里秒内找不到总素数 664579 ----lambda 表达式

我怀疑为什么并行版本比 lambda 版本慢

List<Integer> numbers = new ArrayList<>();
        for (int i = 0; i < 10000000; i++) {
            numbers.add(i);
        }
        Stopwatch stopwatch = Stopwatch.createStarted();
        int counter = 0;
        for (int number : numbers) {
            if (isPrime(number)) {
                counter++;
            }
        }
        stopwatch.stop();
        System.out.println("Total prime no found " + counter + " in " + stopwatch.elapsed(TimeUnit.MILLISECONDS) + " mili sec");

        stopwatch = Stopwatch.createStarted();
        long count1 = numbers.parallelStream().filter(n -> isPrime(n)).count();
        stopwatch.stop();
        System.out.println("Total prime no found " + count1 + " in " + stopwatch.elapsed(TimeUnit.MILLISECONDS) + " mili sec");

        stopwatch = Stopwatch.createStarted();
        long count2 = numbers.stream().filter(n -> isPrime(n)).count();
        System.out.println("Total prime no found " + count2 + " in " + stopwatch.elapsed(TimeUnit.MILLISECONDS) + " mili sec");
        stopwatch.stop();

上述程序使用 google Guava 库计算经过的时间。

【问题讨论】:

  • 在进行基准测试时,您还应该注意预热期
  • 另外:count1count2 是相同的...
  • 我改了,结果不一致
  • 在 4178 英里秒内找不到总质数 664579 --loop
    在 2597 英里秒内找不到总质数 664579 --并行流
    在 4187 英里秒内找不到总质数 664579 --lambda 表达式
    在 4198 mili sec 内找不到总质数 664579 --loop
    在 4690 mili sec 内找不到总质数 664579 -- 在并行流中
    在 4195 mili 内找不到总质数 664579秒 -- 在 lambda 表达式中
    在 4252 英里内找不到总质数 664579 秒 --loop
    在 2455 英里内找不到总质数 664579 秒 --parallel
    在 4555 英里内找不到总质数 664579 sec --lambda 表达式
  • 您的基准测试方法有点简单。您应该使用诸如 jmh 之类的框架。

标签: java lambda parallel-processing java-8


【解决方案1】:

问题很可能是在您的测试期间,JIT 编译器(重新)编译代码。这意味着您的比较不公平,因为后面的测试受益于早期测试引起的编译。

这是微基准测试中经常犯的错误。有很多文章解释了这个问题,例如Robust Java benchmarking。如果我先添加一些代码来预热 JIT,则结果是预期的。我的主要方法如下:

public static void main(String... args) {
    System.out.println("Warmup...");
    for (int i = 0; i < 5000; ++i) {
        run(Demo::testLoop, 5000);
        run(Demo::testStream, 5000);
        run(Demo::testParallel, 5000);
    }
    System.out.println("Benchmark...");
    int bound = 10000000;
    System.out.printf("Loop:     %s\n", run(Demo::testLoop, bound));
    System.out.printf("Stream:   %s\n", run(Demo::testStream, bound));
    System.out.printf("Parallel: %s\n", run(Demo::testParallel, bound));
}

输出如下:

Loop:     7.06s (664579)
Stream:   7.06s (664579)
Parallel: 3.84s (664579)

如果您将选项 -XX:+PrintCompilation 传递给 Java VM,您可以看到 JIT 何时何地启动,并且现在几乎所有编译都发生在预热阶段。

请注意,并行流并不是这种并行化的最佳解决方案,因为 isPrime 的复杂性取决于值。这意味着,序列的前半部分所需的工作量明显少于后半部分(依此类推)。

作为参考,这里是我实现的其余方法:

public static boolean isPrime(int value) {
    for (int i = 2; i * i <= value; ++i)
        if (value % i == 0) return false;
    return true;
}

public static long testLoop(int bound) {
    long count = 0;
    for (int i = 2; i < bound; ++i)
        if (isPrime(i)) ++count;
    return count;
}

public static long testStream(int bound) {
    return IntStream.range(2, bound).filter(Demo::isPrime).count();
}

public static long testParallel(int bound) {
    return IntStream.range(2, bound).parallel().filter(Demo::isPrime).count();
}

public static String run(IntToLongFunction operation, int bound) {
    long start = System.currentTimeMillis();
    long count = operation.applyAsLong(bound);
    long millis = System.currentTimeMillis() - start;
    return String.format("%4.2fs (%d)", millis / 1000.0, count);
}

【讨论】:

  • 你能详细说明什么是 JIT 热身吗?如果我没记错的话,你正在通过那个 for 循环在侧主方法中进行热身。感谢您的详细解释
  • 我添加了一个链接到一篇解释 warmup 的文章。您可以使用命令行选项-XX:+PrintCompilation 来查看发生了什么。
【解决方案2】:

众所周知,F/J 框架需要预热。代码的编写方式只有在编译时才能正常运行。您还必须考虑创建线程所需的时间。在生产环境中有一个预热期是主观的。

框架中有很多糟糕的代码来克服第一次启动时的这种迟缓行为。

【讨论】:

    猜你喜欢
    • 2017-11-16
    • 1970-01-01
    • 1970-01-01
    • 2015-06-01
    • 2015-07-05
    • 1970-01-01
    • 1970-01-01
    • 2015-10-07
    • 1970-01-01
    相关资源
    最近更新 更多