gpt4 book ai didi

Java 并行流未按预期工作

转载 作者:行者123 更新时间:2023-12-01 17:41:38 25 4
gpt4 key购买 nike

我有以下代码:

Map<String, Person> targetPerson = targetPersonList
.stream()
.collect(toMap(Person::getKey, Function.identity()));

其中 targetPersonList 是一个相当大的列表,上面的代码大约需要 38 分钟才能完成。所以我认为下面的代码应该会加快一点速度

Map<String, Person> targetPerson = targetPersonList
.parallelStream()
.collect(toMap(Person::getKey, Function.identity()));

这实际上是相反的“平行”片段,需要 1 小时 20 分钟。我有一个酷睿 i7 8 代,应该有 6 核 12 线程,那么问题是什么?我对并行流的理解有根本性的错误吗?

最佳答案

仅仅填充一个 HashMap 就需要 38 分钟,这是一个不寻常的长时间。它表明,Person::getKey 正在执行昂贵的构造,或者结果是一个具有不太理想的 hashCodeequals 实现的对象.

在我的机器上,使用合理的 hashCodeequals 实现填充一千万个元素的映射只需不到一秒,数亿个元素仍然只需要几秒钟,那么,内存消耗就成为一个问题。

也就是说,并行流的性能较差并不令人意外。正如“Should I always use a parallel stream when possible?”中所讨论的”,并行处理有一些固定的开销,并且您需要一些重要的(每个元素)工作负载才能获得大于开销的 yield 。

在您的具体示例中,根本没有任何好处。

并行collect操作的工作原理是将流元素分割成 block ,由不同的工作线程处理。每个线程都会创建一个新的本地容器,如果是 toMap 则为与最终结果类型相同的映射,然后,每个线程都会将元素累积到其本地容器中,即将值放入映射中,当两个工作线程完成工作时,部分结果将被合并,这意味着将一个映射的所有元素放入另一个映射中。

由于您没有过滤操作并且没有合并功能意味着所有键都是唯一的,因此很容易得出结论:在最好的情况您有两个工作线程填充相同的两个映射完全并行地调整大小,然后将其中一个映射放入另一个映射中,所花费的时间与先前并行处理所节省的时间一样多。

您的示例也不包含可能昂贵的中间操作,因此只有当 Person::getKey 昂贵时,才可以通过并行处理来降低其成本。

this answer 中所述,使用 toConcurrentMap 而不是 toMap 可以改善这种情况,因为它允许跳过合并操作,并且拥有唯一的键意味着当所有工作线程放入时,争用非常低一张 map 。

但是,值得调查性能问题的实际原因。当问题是关键对象的 hashCodeequals 实现时,修复它会带来更多好处。此外,并发无法解决与几乎已满的堆相关的问题。

最后,toConcurrentMap 返回一个并发映射,这可能会给后续处理带来更高的成本,即使您不打算在多个线程中使用此映射。

关于Java 并行流未按预期工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60403434/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com