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

在Java 8中,Collection接口扩展了两个返回Stream<E>stream()返回一个顺序stream, parallelStream()返回一个可能并行的stream。 stream本身也有一个parallel()方法返回一个等效的并行stream(或者将当前stream改变为并行或者创build一个新stream)。

重复有明显的缺点:

  • 这很混乱。 一个问题询问是否需要调用parallelStream()。parallel()来确保该stream是并行的 ,因为parallelStream()可能会返回一个顺序stream。 为什么parallelStream()存在,如果它不能保证? 反过来也是令人困惑的 – 如果parallelStream()返回一个顺序stream,可能有一个原因(例如,一个固有的顺序数据结构,并行stream是一个性能陷阱)。 Stream.parallel()应该为这样的stream做什么? (UnsupportedOperationException不被parallel()的规范所允许。)

  • 如果现有实现具有名称相似的方法,且返回types不兼容,则向接口添加方法可能会产生冲突。 除了stream()之外,还增加了parallelStream(),使得获得增益的风险加倍。 (请注意,parallelStream()是在一个刚刚命名为parallel(),虽然我不知道是否重命名,以避免名称冲突或其他原因。)

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

Collection.(parallelS|s)tream()的Javadocs Collection.(parallelS|s)tream()Stream本身并不回答这个问题,所以它的理由是邮件列表。 我通过了lambda-libs-spec-observers存档,发现了一个专门关于Collection.parallelStream()的线程,另一个线程涉及java.util.Arrays是否应该提供parallelStream()来匹配(或者实际上是否应该是去除)。 没有一劳永逸的结论,所以也许我从另一个名单上漏掉了一些东西,或者这个问题是在私下讨论中解决的。 (也许布赖恩·戈茨 ( Brian Goetz) ,这个讨论的负责人之一,可以填补任何遗漏。)

参与者很好地阐述了他们的观点,所以这个答案大多只是一个相关引用的组织,在[括号]中有一些说明,按照重要性的顺序(按我的理解)。

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

Brian Goetz在第一个线程中解释了为什么Collections.parallelStream()即使在删除了其他并行stream工厂方法之后仍然有足够的价值:

我们没有明确的每个stream程工厂的并行版本。 我们原来是这样做的,为了修剪API表面区域,我们根据从API中删除20多种方法的理论,将它们切割为.intRange(...).parallel()的表面恶意和性能代价, 。 但是我们没有用Collection来做这个select。

我们可以删除Collection.parallelStream() ,也可以添加所有生成器的并行版本,否则我们无能为力。 我认为所有的APIdevise理由都是合理的。

尽pipe不一致,我仍然喜欢现状。 我们没有2Nstream构造方法,而是有N + 1个 – 但是这个额外的1覆盖了大量的情况,因为它被每个集合inheritance。 所以我可以自我辩解为什么有额外的方法是值得的,为什么接受不进一步的不一致是可以接受的。

别人不同意吗? 是N + 1 [Collections.parallelStream()唯一]这里的实际select? 或者我们应该去纯净N [依靠Stream.parallel()] ? 还是2N的便利性和一致性[所有工厂的并行版本] ? 还是有一些更好的N + 3 [Collections.parallelStream()加上其他特殊情况] ,对于其他一些特殊select的情况,我们想给予特别的支持?

Brian Goetz在后面关于Arrays.parallelStream()讨论中站在了这个位置:

我仍然很喜欢Collection.parallelStream; 它具有巨大的发现优势,并且在API表面区域上提供了相当大的回报 – 另外一种方法,但是在很多地方提供了价值,因为Collection将是stream源的真正常见情况。

parallelStream()更高性能

布莱恩·戈茨 :

直接版本[parallelStream()]更高性能,因为它需要较less的包装(将一个stream转换为一个并行stream,您必须先创build顺序stream,然后将其状态的所有权转移到一个新的Stream中。

为了回应Kevin Bourrillion对效果是否显着的怀疑, Brian又说 :

取决于你有多重视。 Doug在进行并行操作的路上统计单个对象创build和虚拟调用,因为直到你开始分叉为止,你对Amdahl定律的错误一面 – 这是在你分叉任何工作之前发生的所有“连续分数”这进一步推动了你的盈亏平衡门槛。 所以快速获得并行操作的设置path是有价值的。

道格利亚跟进 ,但对冲他的立场:

处理平行图书馆支持的人员需要对此进行一些态度调整。 在即将到来的典型机器上,每个浪费你设置并行性的周期花费64个周期。 如果需要64个对象创build开始并行计算,你可能会有不同的反应。

也就是说,我总是完全支持强制执行者为了更好的API而努力工作,只要API不排除有效的实现。 所以如果kill parallelStream真的很重要,我们会find一些方法将stream().parallel()转换成位翻转或者其他的方式。

事实上,稍后关于Arrays.parallelStream()讨论注意到较低的Stream.parallel()成本 。

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

在讨论的时候,从顺序切换到并行和返回的stream可以与其他stream操作交错。 Brian Goetz代表Doug Lea解释了为什么顺序/并行模式切换可能会使Java平台的未来发展复杂化:

我会尽我所能地解释为什么:因为它(如有状态的方法(sorting,区分,限制)),你也不喜欢,使我们逐渐从传统数据方面expressionstreampipe道并行结构,这进一步限制了我们将其直接映射到明天的计算基板的能力,无论是vector处理器,FPGA,GPU还是我们所做的任何事情。

对各种并行计算基板进行过滤 – 映射 – 减less映射; filter-parallel-map-sequential-sorted-limit-parallel-map-uniq-reduce没有。

所以这里的整个APIdevise体现了很多关于用户可能想要expression的东西之间的紧张关系,而且这样做的方式是我们可以通过透明的成本模型来预测快速的。

进一步讨论后,这种模式切换被删除 。 在当前版本的库中,streampipe道是顺序的或并行的; 最后调用sequential() / parallel()获胜。 除了侧置有状态问题之外,这种改变还提高了使用parallel()从顺序stream工厂build立并行stream水线的性能。

揭示parallelStream()作为一stream的公民可以提高程序员对库的感知,使他们编写更好的代码

Brian Goetz再次回应Tim Peierls的观点 ,即Stream.parallel()允许程序员在并行之前顺序地理解stream:

关于这种顺序直觉的价值,我有一个稍微不同的观点 – 我认为普遍的“顺序期望”是整个努力的最大挑战之一; 人们不断地带来他们不正确的顺序偏见,这导致他们做愚蠢的事情,如使用一个元素的数组作为一种方式来“欺骗”“愚蠢的”编译器,让他们捕获一个可变的本地,或使用lambdas作为参数映射在计算过程中使用的变异状态(以非线程安全的方式),然后,当它指出他们在做什么时,摆脱它,并说:“是的,但我没有这样做在平行下。”

我们已经做了很多devise折衷来合并顺序和并行stream。 我相信这个结果是一个干净的结果,并且会增加图书馆十多年来仍然有用的机会,但是我并不是特别喜欢鼓励人们认为这是一个连着一些平行书包的连续图书馆在一边。