通过链接操作快速降低流吞吐量?

时间:2018-10-04 12:08:28

标签: java java-stream throughput

我希望简单的中间流操作(例如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,只是用秒表进行了快速实验)

2 个答案:

答案 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?因此,不要指望这部分会很快有所改善。