【发布时间】:2014-06-22 18:56:12
【问题描述】:
注意:我已经在另一篇 SO 帖子 - Using a semaphore inside a nested Java 8 parallel stream action may DEADLOCK. Is this a bug? - 中解决了这个问题,但这篇帖子的标题暗示这个问题与信号量的使用有关 - 这有点分散了讨论的注意力。我创建这个是为了强调嵌套循环可能存在性能问题——尽管这两个问题可能有一个共同的原因(也许是因为我花了很多时间来解决这个问题)。 (我不认为它是重复的,因为它强调了另一种症状 - 但如果你确实删除它)。
问题:如果嵌套两个Java 8 stream.parallel().forEach 循环并且所有任务都是独立的、无状态的等等——除了提交到公共FJ池——那么嵌套并行循环内的并行循环的性能比将顺序循环嵌套在并行循环内要差得多。更糟糕的是:如果包含内部循环的操作是同步的,你会得到一个死锁。
性能问题演示
没有“同步”,您仍然可以观察到性能问题。您可以在以下位置找到演示代码:http://svn.finmath.net/finmath%20experiments/trunk/src/net/finmath/experiments/concurrency/NestedParallelForEachTest.java (有关更详细的说明,请参阅那里的 JavaDoc)。
我们这里的设置如下:我们有一个嵌套的 stream.parallel().forEach()。
- 内部循环是独立的(无状态、无干扰等 - 使用公共池除外)并且在最坏的情况下总共消耗 1 秒,即如果按顺序处理。
- 外循环的一半任务在该循环之前消耗 10 秒。
- 该循环后一半消耗 10 秒。
- 因此每个线程总共消耗 11 秒(最坏情况)。 * 我们有一个布尔值,它允许将内部循环从并行()切换到顺序()。
现在:将 24 个外循环任务提交到并行度为 8 的池中,我们预计最多 24/8 * 11 = 33 秒(在 8 核或更好的机器上)。
结果是:
- 使用内部顺序循环:33 秒。
- 使用内部并行循环:>80 秒(我有 92 秒)。
问题:您能确认一下这种行为吗?这是人们对框架的期望吗? (我现在更加小心了,声称这是一个错误,但我个人认为这是由于 ForkJoinTask 的实现中的一个错误。备注:我已将此发布到并发兴趣(请参阅http://cs.oswego.edu/pipermail/concurrency-interest/2014-May/012652.html) ,但到目前为止我还没有从那里得到确认)。
僵局演示
下面的代码会死锁
// Outer loop
IntStream.range(0,numberOfTasksInOuterLoop).parallel().forEach(i -> {
doWork();
synchronized(this) {
// Inner loop
IntStream.range(0,numberOfTasksInInnerLoop).parallel().forEach(j -> {
doWork();
});
}
});
numberOfTasksInOuterLoop = 24、numberOfTasksInInnerLoop = 240、outerLoopOverheadFactor = 10000 和 doWork 是一些无状态 CPU 刻录机。
您可以在 http://svn.finmath.net/finmath%20experiments/trunk/src/net/finmath/experiments/concurrency/NestedParallelForEachAndSynchronization.java 找到完整的演示代码 (有关更详细的说明,请参阅那里的 JavaDoc)。
这是预期的行为吗?请注意,有关 Java 并行流的文档没有提到任何嵌套或同步问题。此外,没有提到两者都使用共同的分叉连接池这一事实。
更新
另一个关于性能问题的测试可以在http://svn.finmath.net/finmath%20experiments/trunk/src/net/finmath/experiments/concurrency/NestedParallelForEachBenchmark.java找到 - 这个测试没有任何阻塞操作(没有 Thread.sleep 和不同步)。我在这里整理了一些评论:http://christian-fries.de/blog/files/2014-nested-java-8-parallel-foreach.html
更新 2
似乎这个问题和更严重的信号量死锁已经在 Java8 u40 中得到修复。
【问题讨论】:
-
也许值得链接到并发兴趣讨论:cs.oswego.edu/pipermail/concurrency-interest/2014-May/…
-
那个并发兴趣讨论很奇怪。令我震惊的是,讨论中的所有成员都坚持这样的事情:“程序员应该知道 F/J 执行 xyz”或“程序员应该使用来自 F/ 的
for this ”,但通过搜索entire documentation ofjava.util.stream,我发现根本没有提到 F/J 框架。我记得在某处看到过将 F/J 作为实现细节这样的提及,但那是完全不同的画面。 -
@Christian Fries 关于这个问题有任何消息吗?
标签: java concurrency parallel-processing java-8 java-stream