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