我希望简单的中间流操作(例如limit()
)的开销很小。但是这些示例之间的吞吐量差异实际上是很明显的:
final long MAX = 5_000_000_000L;
LongStream.rangeClosed(0, MAX)
.count();
// throughput: 1.7 bn values/second
LongStream.rangeClosed(0, MAX)
.limit(MAX)
.count();
// throughput: 780m values/second
LongStream.rangeClosed(0, MAX)
.limit(MAX)
.limit(MAX)
.count();
// throughput: 130m values/second
LongStream.rangeClosed(0, MAX)
.limit(MAX)
.limit(MAX)
.limit(MAX)
.count();
// throughput: 65m values/second
我很好奇:吞吐量快速下降的原因是什么?它是与链式流操作或测试设置保持一致的模式吗? (到目前为止,我还没有使用JMH,只是用秒表进行了快速实验)
答案 0 :(得分:4)
limit
将导致由 slice 组成的流,并带有 split迭代器(用于并行操作)。一句话:效率低下。无操作的开销很大。而且两个连续的limit
调用导致两个切片会很可惜。
您应该看一下IntStream.limit
的实现。
由于Streams仍然是相对较新的,因此优化应该排在最后;生产代码存在时。进行3次极限似乎有些牵强。
答案 1 :(得分:4)
这是Stream API中的一个底层实现(不知道如何调用它)。
在第一个示例中,您知道 count
却没有实际计数-例如,没有filter
操作可能会清除称为{{1 }}。如果您对此进行更改并检查,实际上有点有趣:
SIZED
并且System.out.println(
LongStream.rangeClosed(0, Long.MAX_VALUE)
.spliterator()
.hasCharacteristics(Spliterator.SIZED)); // reports false
System.out.println(
LongStream.rangeClosed(0, Long.MAX_VALUE - 1) // -1 here
.spliterator()
.hasCharacteristics(Spliterator.SIZED)); // reports true
-即使没有基本(AFAIK)限制,也不会引入limit
标志:
SIZED
由于您无处不在,因此Stream API在内部不知道stream是否为System.out.println(LongStream.rangeClosed(0, MAX)
.limit(MAX)
.spliterator()
.hasCharacteristics(Spliterator.SIZED)); // reports false
,这一点很重要。而如果Stream是SIZED
-报告计数将是即时的。
几次添加SIZED
只会使情况变得更糟,因为它必须每次都限制这些限制。
例如,对于Java-9,情况有所改善:
limit
在这种情况下,System.out.println(LongStream.rangeClosed(0, MAX)
.map(x -> {
System.out.println(x);
return x;
})
.count());
根本不会计算,因为不需要它-无需任何中间操作即可更改流的大小。
从理论上讲,Stream API可能会看到您正在map
上,并且1)引入了limit
标志2)看到您有多个对SIZED
的调用,并且可能只接受了最后一个。 目前还没有完成,但是范围很有限,有多少人会这样滥用limit
?因此,不要指望这部分会很快有所改善。