当.stream()。parallel()做同样的事情时,为什么Collection.parallelStream()存在?

时间:2014-07-07 04:44:21

标签: java java-8 java-stream

在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()做同样的事情?

1 个答案:

答案 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,这个讨论的主要内容之一,可以填补任何遗漏的内容。)

参与者表达了他们的观点,所以这个答案大多只是一个相关引用的组织,在 [方括号] 中有一些澄清,按重要性顺序呈现(正如我所解释的那样)

parallelStream()涵盖了一个非常常见的情况

第一个帖子中的

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()性能更高

Brian Goetz

  

直接版 [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的讨论。

stream()。parallel()有状态使未来复杂化

在讨论时,将流从顺序切换到并行并返回可以与其他流操作交错。 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()从顺序流工厂设置并行管道的性能。

将parallelStream()作为一等公民公开,提高了程序员对库的感知,使他们能够编写更好的代码

Brian Goetz again,以回应Tim Peierls's argument Stream.parallel()允许程序员在并行之前顺序理解流:

  

我对这个序列的价值有一点看法   直觉 - 我认为普遍的&#34;连续的期望&#34;如果一个人   这整个努力面临的最大挑战;人们经常   带来不正确的顺序偏差,导致他们做傻事   比如使用单元素数组作为一种方法来欺骗&#34; &#34;愚蠢&#34;   编译器让他们捕获一个可变的本地,或使用lambda作为   用于映射将在其中使用的变异状态的参数   计算(以非线程安全的方式),然后,当它指出   他们正在做什么,耸耸肩说'是的,但是我不做   它并行。&#34;

     

我们进行了许多设计权衡以合并顺序和并行   流。我相信,结果是一个干净的结果,并将增加   图书馆在10年以上仍然有用的机会,但我不知道   特别喜欢鼓励人们认为这是一个   顺序库,侧面钉着一些平行袋。