通过链式操作快速降低流吞吐量?
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 可能会看到您正在 limit
ing 并且 1) 引入 SIZED
标志 2) 看到您有多个 limit
调用并且可能只拿最后一个。 目前这还没有做,但是范围非常有限,有多少人会滥用limit
这种方式?所以不要指望这部分很快会有任何改进。
我预计简单的中间流操作(例如 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 可能会看到您正在 limit
ing 并且 1) 引入 SIZED
标志 2) 看到您有多个 limit
调用并且可能只拿最后一个。 目前这还没有做,但是范围非常有限,有多少人会滥用limit
这种方式?所以不要指望这部分很快会有任何改进。