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