【问题标题】:Java 8 Stream Pipeline performanceJava 8 流管道性能
【发布时间】:2017-07-14 21:46:18
【问题描述】:

我对在 java-08 中使用 Stream API 的性能增强有一些疑问。 以下是java-06中的代码。

int sum = 0;
    for (int x : numbers) {
       sum += x;
    }

这是java-8中的代码。

 int sum = numbers.stream().reduce(0, (x,y) -> x+y);

或:

int sum = numbers.stream().reduce(0, Integer::sum);

问题:- 虽然两个代码中的行数相同,但内部操作是如何进行的? 这就是它转换为流和并行处理的方式。

【问题讨论】:

  • 查看oracle doc docs.oracle.com/javase/8/docs/api/java/util/stream/…中的减分操作
  • 方式太宽泛了……而且可能很多很好的搜索已经包含了你的答案
  • @Eugene 如果你觉得你的回答很无聊,那么请给出一些有趣的解决方案。
  • 这不是“无聊”,而是太宽泛。这些是完全不同的事情
  • 好的。给我一些链接或文档参考,我可以在其中找到我的答案。我知道这有点无聊,但对于建筑师来说这是具有挑战性的问题

标签: java java-8


【解决方案1】:

首先,您的流不是并行流。您必须明确调用 List#parallelStreamStream#parallel。例如:

int sum = numbers.parallelStream().reduce(0, Integer::sum);

另一种求和numbers 的方法是将Stream<Integer> 映射到IntStream,它会取消装箱N 次,但Stream#reduce 会取消装箱2 *(N - 1) 次,如果流大小> 2、例如:

int sum = numbers.parallelStream().mapToInt(Integer::intValue).sum();

对于“内部运行情况如何?”,你可以看Eran的回答,据我所知他已经详细描述了并行流。 p>

Stream#reduce 拆箱

1 + 2 + 3 + 4 + 5减少树的例子如下,拆箱操作次数:N = 101(2) + 5(2) + 9(2) + 6(2) + 15(2)):

// v--- identity
   0      1     2       3     4       5
     1(2)          5(2)          9(2)
            6(2)          9(/)
                  15(2) 
//                ^  ^--- unboxing times, `/` means doesn't reducing at this time
//                |
//                |---  the sum result of current reducing  

【讨论】:

  • Stream 减少 uboxing 2 *(N - 1) 次你能解释一下吗?
  • @HasnainAliBohra 嗨,Eran 的答案已经在树中描述了。
  • @HasnainAliBohra 怎么样,先生?
  • @HasnainAliBohra :),一点也不。
  • 为什么是11(0)?请注意,当您使用带有标识元素的变体reduce(0, Integer::sum) 时,标识元素还将与其他流元素组合,因此您有2*N 拆箱和N 装箱操作——在顺序情况下。对于并行缩减,每个生成的子任务都将使用标识元素作为起点,因此您将进行更多的装箱和拆箱。
【解决方案2】:

.reduce(0, (x,y) -> x+y).reduce(0, Integer::sum) 之间没有性能差异。差异在编译时处理:

对于 lambda 表达式,将生成一个包含 lambda 主体的合成方法,x+y。相比之下,Integer::sum 指的是现有方法,其代码完全相同。从那时起,发生的一切都将是一样的。您的类将请求 JRE 生成功能接口的实现,其功能方法将调用指定的方法、合成方法或现有方法,两者都做同样的事情。

因此,无论哪种情况,您最终都会得到一个 JRE 生成的 BinaryOperator,它将调用一个返回两个 int 参数之和的方法。由于没有技术差异,因此不可能有任何性能差异。

但作为holi-java correctly pointed out,两种变体都包含不必要的装箱开销。 BinaryOperator<Integer> 接收两个 Integer 实例,如果两者都是现有的实例,这很好,源于集合,但也会返回一个 Integer,这将是输入总和的盒装表示。该总和可能会被传递到 BinaryOperator<Integer> 的下一次评估中,再次被拆箱。

相比之下,

int sum = numbers.stream().mapToInt(Integer::intValue).sum();

只需要从集合中拆箱,但永远不会再装箱或拆箱中间总和。请注意,在使用 parallelStream() 而不是 stream() 之前,您需要相当多的元素。

【讨论】:

  • 怎么说不是中级拆箱。有oracle参考吗??
  • @Hasnain Ali Bohra:mapToInt 返回一个IntStream。整个类型的存在仅用于使用原始 int 值而不是对象进行操作。
  • @Holger 优先,赞成。是的,这就是我想说的。由于我的英语不好......我说的很简单。
  • 所以在这种情况下,在执行时不会对 IntStream 进行拆箱。因为它仅使用 int 进行处理。所以 IntStream 的输出是 int ,如果我将它分配给 Integer 然后装箱就会发生。
  • @Hasnain Ali Bohra:如前所述,收藏中的物品必须拆箱,这是不可避免的。 mapToInt(Integer::intValue) 会发生这种情况。然后,IntStream.sum() 将在没有任何装箱或拆箱的情况下进行总结。相反,使用Stream.reduce,每个求值步骤都必须再次返回一个对象,因此(x,y)->x+yInteger::sum 将使用int 进行计算,但随后结果将被装箱到Integer 对象。
猜你喜欢
  • 2014-05-04
  • 1970-01-01
  • 2014-07-24
  • 1970-01-01
  • 1970-01-01
  • 2015-02-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多