使用嵌套的 Intstream 循环时,Java 8 性能非常糟糕

2022-09-03 15:19:28

在阅读了Java 8的java.util.stream.Intstream之后,我一直在用流替换一些传统的循环。不幸的是,我在处理嵌套循环时遇到了一些性能问题。

不出所料,以下代码在我的计算机中大约在 47 毫秒内运行:

IntStream.range(0, 1000000000).forEach(i -> {});

但是,嵌套另一个 IntStream 超处理器会将执行时间膨胀到大约 10,458 毫秒 - 即:

IntStream.range(0, 1000000000).forEach(i -> {
    IntStream.range(0, 1).forEach(j -> {});
});

这是我的误用情况,还是将来可能解决的问题?

编辑:为了进行比较,以下代码使用传统的内部循环运行得更快(在1,801毫秒内)。因此,即使考虑到优化,使用内部IntStream似乎也会产生更多的开销?

final long[] random = {1};
IntStream.range(0, 1000000000).forEach(i -> {
    for (int j = 0; j < 1; j++) {
        random[0] += i;
    }
});

答案 1

在第二种情况下,这不是糟糕的表现。这实际上是第一种情况下令人难以置信的出色表现。看,您迭代了超过 10 亿个元素,而迭代仅需 47 毫秒。因此,在一秒钟内,您可以迭代超过1000/47 = 210亿个元素!CPU的频率可能约为3 GHz,因此您可以在单个CPU周期中迭代7个元素!这种优化是由JIT编译器执行的,用于非常简单的循环(实际上它在死代码消除期间绝对优化)。但是,您不会通过编写空循环来赚钱。如果您至少添加一些重要的逻辑,则某些优化将关闭或变得不那么有效,因此您的性能将显着下降。

我建议您对实际代码执行测试,并针对最慢的部分分析应用程序。人工示例与生产代码的实际性能没有任何共同之处。


答案 2

来自 java 文档

void forEach(IntConsumer 操作)对此流的每个元素执行一个操作。这是一个终端操作。

终端操作(如 Stream.forEach 或 IntStream.sum)可能会遍历流以产生结果或副作用。执行终端操作后,流水线视为已消耗,无法再使用;如果需要再次遍历同一数据源,则必须返回到数据源以获取新流。在几乎所有情况下,终端操作都是急切的,在返回之前完成对数据源的遍历和管道的处理。只有终端操作迭代器()和拆分器()不是;它们作为“转义舱口”提供,以便在现有操作不足以完成任务时启用任意客户端控制的管道遍历。

创建大量流会产生开销。您是否尝试过使用探查器运行代码?


推荐