【问题标题】:Java Parallel Stream Slower than SerialJava 并行流比串行慢
【发布时间】:2022-06-16 01:32:27
【问题描述】:

我有大约 1000000 个段落的数据库记录,每个段落大约 500 个字符。通过阅读所有记录,我需要得到按使用次数从多到少排序的字母列表。

我通过创建多达 1000000 个流来模拟数据库读取,然后并行处理流

final Map<Character, Long> charCountMap = new ConcurrentHashMap<>();
for (char c = 'a'; c <= 'z'; c++) {
    charCountMap.put(c, 0l);
}

System.out.println("Parallel Stream");
long start = System.currentTimeMillis();
Stream.iterate(0, i -> i).limit(1000000).parallel() //mock database stream
    .forEach(i-> RandomStringUtils.randomAlphanumeric(500)
    .toLowerCase().chars().mapToObj(c -> Character.valueOf((char) c)).filter(c -> c >= 97 && c <= 122)
    .forEach(c -> charCountMap.compute(c, (k, v) -> v + 1))); //update ConcurrentHashMap

long end = System.currentTimeMillis();
System.out.println("Parallel Stream time spent :" + (end - start));

System.out.println("Serial Stream"); start = System.currentTimeMillis();
Stream.iterate(0, i -> i).limit(1000000) //mock database stream
    .forEach(i-> RandomStringUtils.randomAlphanumeric(500)
    .toLowerCase().chars().mapToObj(c -> Character.valueOf((char) c)).filter(c -> c >= 97 && c <= 122)
    .forEach(c -> charCountMap.compute(c, (k, v) -> v + 1)));
end = System.currentTimeMillis();
System.out.println("Serial Stream time spent :" + (end - start));

我最初认为并行流会更快,即使流大于 100,000 的预期开销也是如此。然而,测试表明即使处理 1,000,000 条记录,串行流也比并行快约 5 倍。

我怀疑是因为更新了 ConcurrentHashMap。但是当我删除它并用空函数更改时,仍然存在显着的性能差距。

我的数据库模拟调用或我使用并行流的方式有什么问题吗?

【问题讨论】:

  • 这里是凭记忆,所以要持保留态度,但是在计算经过的时间时,您应该使用System.nanoTime(),而不是System.currentTimeMillis()。如果您必须自己进行基准测试,这个question 是一个很好的来源。
  • 如果没有热身运行和适当的基准测试,您将无法得出任何结论。只需尝试交换串行和并行代码部分的顺序,您可能会得出相反的结论,因为第一次迭代测试最慢。此外,随机生成器可能不是线程安全的(无法判断,因为您没有显示代码),这可能意味着并行版本永远不会是最快的。
  • 我已经交换了订单并得到了相同的结果。我使用的 RandomStringUtils 来自 Apache commons-lang 库

标签: java concurrency stream parallelstream


【解决方案1】:

使用RandomStringUtils.randomAlphanumeric(500) 不适合与parallel() 一起使用,因为根据code here,它使用静态变量来生成随机字符串。因此,所有线程生成随机字符串的所有调用都将争用一个实例的同一底层实例:

private static final Random RANDOM = new Random();

编写您自己的随机字符串生成器,每个线程使用Random 的单个实例或使用ThreadLocalRandom - 这避免了随机序列的争用。同样的问题导致question 的性能不佳。

查看Random 的javadoc 说:

However, the concurrent use of the same {@code java.util.Random}
instance across threads may encounter contention and consequent
poor performance. Consider instead using
{@link java.util.concurrent.ThreadLocalRandom} in multithreaded
designs.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-29
    • 1970-01-01
    • 2014-07-24
    相关资源
    最近更新 更多