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

Quickly degrading stream throughput with chained operations?

我预计简单的中间流操作(例如 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,只是用秒表做了一个快速实验)

limit 将导致 slice 由流组成,带有 split iterator(用于并行操作) .一言以蔽之:效率低下。这里的空操作的开销很大。两次连续的 limit 调用导致两个切片是一种耻辱。

你应该看看 IntStream.limit 的实现。

由于 Streams 相对较新,优化应该排在最后;当生产代码存在时。做limit 3次好像有点牵强

这是 Stream API 中的一个底层实现(否则不知道如何调用它)。

在第一个示例中,您 知道 count 而无需实际计数 - 没有 filter(例如)可能清除内部的操作名为 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

limit - 即使没有基本(AFAIK)限制,也不会引入 SIZED 标志:

System.out.println(LongStream.rangeClosed(0, MAX)
            .limit(MAX)
            .spliterator()
            .hasCharacteristics(Spliterator.SIZED)); // reports false

因为你到处都在计数,事实上 Stream API 不知道流是否是 SIZED,它只是计数;而如果 Stream 是 SIZED - 报告计数会很好,即时。

当您添加 limit 几次时,只会让情况变得更糟,因为它必须限制这些限制,每一次。

java-9 中的情况有所改善,例如:

System.out.println(LongStream.rangeClosed(0, MAX)
            .map(x -> {
                System.out.println(x);
                return x;
            })
            .count());

在这种情况下 map 根本不计算,因为不需要它 - 没有中间操作改变流的大小。

理论上 Stream API 可能会看到您正在 limiting 并且 1) 引入 SIZED 标志 2) 看到您有多个 limit 调用并且可能只拿最后一个。 目前这还没有做,但是范围非常有限,有多少人会滥用limit这种方式?所以不要指望这部分很快会有任何改进。