在Java 8中,用两个返回Stream<E>
的方法扩展了Collection接口:stream()
返回一个顺序流,而parallelStream()
返回一个可能并行的流。 Stream本身还具有parallel()
方法,该方法返回等效的并行流(将当前流更改为并行或创建新流)。
复制有明显的缺点:
令人困惑。给定parallelStream()可能返回顺序流的问题,因此会问whether calling both parallelStream().parallel() is necessary to be sure the stream is parallel。如果不能保证parallelStream()为什么存在,为什么?另一种方法也是令人困惑的-如果parallelStream()返回顺序流,则可能是有原因的(例如,并行流本身就是性能陷阱的固有顺序数据结构); Stream.parallel()对这样的流应该做什么? (parallel()的规范不允许UnsupportedOperationException。)
如果现有实现具有名称相似且返回类型不兼容的方法,则将方法添加到接口可能会发生冲突。除了stream()之外,添加parallelStream()会使获得很少收益的风险加倍。 (请注意,parallelStream()在某一时刻仅被命名为parallel(),尽管我不知道是否对其进行了重命名以避免名称冲突或其他原因。)
为什么在调用Collection.stream()。parallel()时存在Collection.parallelStream()同样的事情?
最佳答案
Collection.(parallelS|s)tream()
和Stream
的Javadocs本身无法回答问题,因此有关原理的信息已转至邮件列表。我浏览了lambda-libs-spec-observers档案,找到了one thread specifically about Collection.parallelStream()和另一个线程,该线程触及java.util.Arrays should provide parallelStream()是否匹配(或者实际上是否应该删除)。没有一劳永逸的结论,因此也许我错过了另一个清单中的某些内容,或者此事已在私人讨论中解决。 (也许Brian Goetz是此讨论的原理之一,可以弥补任何遗漏的内容。)
参与者的观点很好,因此,答案基本上只是相关引语的组织,在[方括号]中有一些澄清,以重要性的顺序表示(据我解释)。
parallelStream()涵盖了一个非常常见的情况
第一个线程中的Brian Goetz,解释了为什么即使删除了其他并行流工厂方法后,Collections.parallelStream()
仍然足以保留的价值:
我们没有每个[流工厂]的显式并行版本。我们做了
最初,为了减少API表面积,我们在
从API中删除20多种方法值得进行权衡的理论
.intRange(...).parallel()
的表面皱纹和性能成本。
但是我们没有使用Collection做出选择。
我们可以删除Collection.parallelStream()
,也可以添加
所有生成器的并行版本,否则我们什么也做不了,
保持原样。我认为所有这些在API设计方面都是合理的。
尽管有点不一致,但我还是很喜欢现状。代替
有2N个流构建方法,我们有N + 1个-但是那额外的1个
涵盖了很多情况,因为它是每个人都继承的
采集。所以我可以为自己辩解为什么要有这种额外的1种方法
是值得的,为什么接受不再走一步的矛盾是
可以接受的。
别人不同意吗? N + 1 [仅适用于Collections.parallelStream()]是这里的实际选择吗?还是应该去
N的纯度[依靠Stream.parallel()]?还是2N(所有工厂的并行版本)的便利性和一致性?或者是
还有一些更好的N + 3 [Collections.parallelStream()以及其他特殊情况],对于其他一些特殊情况,我们
想给予特别支持吗?
Brian Goetz在后面有关Arrays.parallelStream()
的讨论中坚持这一立场:
我还是很喜欢Collection.parallelStream;它有巨大的
可发现性的优势,并在API上带来可观的回报
表面积-另一种方法,但可以在许多地方提供价值,
因为Collection是流源的真正常见情况。
parallelStream()性能更高
Brian Goetz:
直接版本[parallelStream()]的性能更高,因为它需要更少的包装(
将流变成并行流,您必须先创建
顺序流,然后将其状态的所有权转移到新
流。)
为了回应Kevin Bourrillion关于效果是否显着的怀疑,Brian again:
取决于您计数的认真程度。道格计算单个对象
在进行并行操作的过程中进行创建和虚拟调用,
因为在开始分叉之前,您处于Amdahl的错误立场
法律-这就是在分叉之前发生的所有“序列分数”
任何工作,将您的收支平衡极限进一步提高。所以得到
快速并行操作的设置路径很有价值。
Doug Lea follows up,但对冲他的位置:
处理并行库支持的人员需要一些态度
调整这类事情。在即将成为典型的机器上,
您浪费的每个周期设置并行度都需要花费64个周期。
如果需要64,您可能会有不同的反应
对象创建以开始并行计算。
也就是说,我始终完全支持强制实施者
为了更好的API而努力工作,只要
API并不排除有效的实现。所以如果杀死
parallelStream
非常重要,我们将找到一些方法来
将stream().parallel()
变成位翻转。
确实,稍后有关Arrays.parallelStream()
takes notice of lower Stream.parallel() cost的讨论。
stream()。parallel()有状态使未来复杂化
在讨论时,可以将流从顺序切换到并行再切换回去可以与其他流操作交错进行。 Brian Goetz, on behalf of Doug Lea,解释了为什么顺序/并行模式切换会使Java平台的未来开发变得复杂:
我将竭尽全力解释原因:因为它(例如有状态
您也不喜欢的方法(排序,不同,限制))
距离能够表达流管道越来越远
传统数据并行构造的术语,这进一步限制了
我们将它们直接映射到明天的计算基础上的能力,
无论是矢量处理器,FPGA,GPU还是我们自己准备的东西。
Filter-map-reduce映射非常干净地用于各种并行计算
基材过滤器并行映射顺序排序限制并行映射uniq减少
才不是。
因此,这里的整个API设计在设计之间体现出许多张力
易于表达用户可能想要表达的事物,并且正在做
可以使我们以透明的成本快速实现预期目标
楷模。
此模式切换为removed after further discussion。在该库的当前版本中,流管道是顺序的或并行的。对sequential()
/ parallel()
的最后一次呼叫获胜。除了回避状态问题之外,此更改还提高了使用parallel()
设置顺序流工厂的并行管道的性能。
将parallelStream()公开为一等公民可以提高程序员对库的认识,从而使他们编写更好的代码
Brian Goetz again,作为对Tim Peierls's argument的回应,Stream.parallel()
允许程序员在并行之前顺序地了解流:
我对这个顺序的价值略有不同的看法
直觉-我认为普遍的“顺序期望”是一种
这整个工作的最大挑战;人们不断
带来不正确的顺序偏见,这会使他们变得愚蠢
例如使用单元素数组来“欺骗”“愚蠢”的方法
编译器让他们捕获可变的本地变量,或者使用lambda作为
映射将在
计算(以非线程安全的方式),然后指出
他们在做什么,耸耸肩说:“是的,但我没有
并行进行。”
我们进行了很多设计折衷,以合并顺序和并行
流。我认为,结果是干净的,并将增加
图书馆在十年以上仍然有用的机会,但我没有
特别像鼓励人们认为这是一个
顺序库,侧面装有一些平行袋。