迭代器与Java 8流

Miguel Gamboa:

为了利用java.util.streamJdk 8中包含的各种查询方法,我尝试设计一种域模型,其中具有*多重性(具有零个或多个实例)的关系的获取器返回a Stream<T>而不是Iterable<T>or Iterator<T>

我的疑问是Stream<T>,与相比,是否还会产生任何额外的管理费用Iterator<T>

那么,使用损害我的域模型是否有任何缺点Stream<T>

或者,我是否应该始终返回Iterator<T>Iterable<T>,并通过将迭代器与转换为最终结果,让最终用户选择是否使用流的决定StreamUtils

请注意,返回a Collection是无效的选项,因为在这种情况下,大多数关系都是惰性的并且大小未知。

布莱恩·格茨(Brian Goetz):

这里有很多性能建议,但可悲的是,其中大部分是猜测,几乎没有指向真正的性能注意事项。

@Holger 通过指出我们应该抵抗看似压倒性的趋势,让性能拖尾API设计狗,来正确地对待

尽管有成千上万的考虑因素可以使流在任何给定情况下都比某种形式的遍历慢,相同或比其他遍历慢,但有一些因素表明,流具有其优势的性能优势-在很大程度上数据集。

有一些额外的固定启动开销创建一个Stream比创造一个Iterator-你开始计算前几个对象。如果您的数据集很大,那就没关系;这是一笔很小的启动费用,需要大量计算来摊销。(如果你的数据集是小,这或许也并不重要-因为如果你的程序在小数据集运行,性能一般不是你的#1关心无论是。)凡本问题是什么时候并行?任何花费在建立管道上的时间都将成为阿姆达尔定律的系列部分;如果您看一下实现,我们会努力在流设置过程中减少对象计数,但是我很乐于找到减少它的方法,因为这直接影响了盈亏平衡数据集的大小,并行开始赢得了成功。顺序的。

但是,比固定启动成本更重要的是每个元素的访问成本。在这里,信息流实际上是赢钱的,而且通常会赢很多,有些人可能会感到惊讶。(在性能测试中,我们通常会看到流管道的性能优于对等管道的for循环Collection。)而且,对此有一个简单的解释:Spliterator从根本上说,每个元素的访问成本比Iterator,甚至顺序都要低有几个原因。

  1. 从根本上讲,迭代器协议的效率较低。它需要调用两个方法来获取每个元素。此外,由于迭代器必须对诸如next()不带hasNext()hasNext()不带多次调用之类的事物具有鲁棒性next(),因此这两种方法通常都必须进行防御性编码(通常具有更多的有状态性和分支性),这增加了效率。另一方面,即使使用慢速方式遍历分离器(tryAdvance)也没有此负担。(对于并发数据结构,甚至更糟,因为next/ hasNext二元性从根本上讲是不合理的,并且Iterator实现必须比实现更承担更多的工作来防御并发修改Spliterator。)

  2. Spliterator进一步提供了“快速路径”迭代- forEachRemaining可以在大多数时间使用(缩减,forEach),从而进一步减少了中介代码访问数据结构内部的迭代代码的开销。这也倾向于很好地内联,这反过来又提高了其他优化的有效性,例如代码运动,边界检查消除等。

  3. 此外,遍历过的Spliterator趋向于堆写入的数量要少于with Iterator使用Iterator,每个元素都会导致一个或多个堆写入(除非Iterator可以通过转义分析对其进行标量,并将其字段提升到寄存器中。)除其他问题外,这还会导致GC卡标记活动,从而导致卡标记的缓存行争用。另一方面,Spliterators倾向于具有较少的状态,而工业强度forEachRemaining实现则倾向于将任何内容写到堆中,直到遍历结束为止,而不是将其迭代状态存储在自然映射到寄存器的本地中,从而减少了内存总线的活动。 。

摘要:不用担心,要开心。即使没有并行性也Spliterator更好Iterator(它们通常也更容易编写,更难弄错。)

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章