0

I have some query regarding the performance enhancement using Stream API in java-08. Following is the code in java-06.

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

This is the code in java-8.

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

or:

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

Question :- though the number of lines in the both code is same but how the internal operation is going on ? That is how it is converting to stream and processing parallel.

Hasnain Ali Bohra
  • 2,130
  • 2
  • 11
  • 25

2 Answers2

4

First, your stream is not a parallel stream. you must call List#parallelStream or Stream#parallel explicitly. for example:

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

A another way to summing the numbers is map a Stream<Integer> to a IntStream, it does unboxing N times but Stream#reduce does unboxing 2 *(N - 1) times and additional boxing operations if the stream size > 2, for example:

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

for the "how the internal operation is going on ?" , you can see the Eran's answer, he has described the parallel stream in detailed as far as I see.

Stream#reduce unboxing

the example of 1 + 2 + 3 + 4 + 5 reducing tree as below, the unboxing operation times: N = 10 ( 1(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  
holi-java
  • 29,655
  • 7
  • 72
  • 83
  • Stream **reduce does uboxing 2 *(N - 1) times** can you explain how ? – Hasnain Ali Bohra Jul 14 '17 at 12:51
  • @HasnainAliBohra hi, the Eran's answer is already described that in the tree. – holi-java Jul 14 '17 at 12:52
  • @HasnainAliBohra how about it now, sir? – holi-java Jul 14 '17 at 12:58
  • @HasnainAliBohra :), Not at all. – holi-java Jul 14 '17 at 13:00
  • 1
    Why `11(0)`? Note that when you use `reduce(0, Integer::sum)`, the variant with an identity element, the identity element will also combined with the other stream elements, so you have `2*N` unboxing and `N` boxing operations—in the sequential case. For parallel reduction, every spawned subtask will use the identity element as starting point, so you’ll have even more boxing and unboxing going on. – Holger Jul 14 '17 at 13:31
  • @Holger because `Integer#sum` accept 2 numbers, so I calculate the reducing unboxing times base on that method. `11(0)` means it doesn't reducing at this time. Am I right, sir? and the identity in the tree example is `1` rather than `0`.... – holi-java Jul 14 '17 at 13:33
1

There is no performance difference between .reduce(0, (x,y) -> x+y) and .reduce(0, Integer::sum). The differences are handled at compile time:

For the lambda expression, a synthetic method will be generated holding the lambda’s body, x+y. In contrast, Integer::sum refers to an existing method, which has exactly the same code. From that point, all that happens, will be the same. Your class will request the JRE to generate an implementation of the functional interface whose function method will invoke the specified method, the synthetic method or the existing one, both doing the same.

So in either case, you will end up with a JRE generated BinaryOperator which will invoke a method which returns the sum of both int arguments. Since there is no technical difference, there can’t be any performance difference.

But as holi-java correctly pointed out, both variants incorporate unnecessary boxing overhead. A BinaryOperator<Integer> receives two Integer instances, which is fine if both are existing ones, stemming from the collection, but will also return an Integer, which will be the boxed representation of the sum of the input. That sum might get passed into the next evaluation of the BinaryOperator<Integer>, being unboxed again.

In contrast,

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

only has to unbox the objects from the collection, but will never box or unbox intermediate sums again. Note that you need a fairly large number of elements, before using parallelStream() instead of stream() will pay off.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • How can you say that it is not unboxing at intermediate level. Is there is any oracle reference ?? – Hasnain Ali Bohra Jul 14 '17 at 13:08
  • @Hasnain Ali Bohra: `mapToInt` returns an [`IntStream`](https://docs.oracle.com/javase/8/docs/api/?java/util/stream/IntStream.html). The whole type exists only to do the operations with primitive `int` values instead of objects. – Holger Jul 14 '17 at 13:10
  • @Holger first,up-vote. yeah, that is what I want to say. due to my bad english.... I say it simply. – holi-java Jul 14 '17 at 13:14
  • so in that case there is no unboxing for IntStream while performing. As it is processing only with **int**. so the output from IntStream is int and if im assigning it to the `Integer` Then boxing will take place. – Hasnain Ali Bohra Jul 14 '17 at 13:15
  • 1
    @Hasnain Ali Bohra: as said, the objects from the collection have to be unboxed, that’s unavoidable. This happens with `mapToInt(Integer::intValue)`. Then, `IntStream.sum()` will sum up without any boxing or unboxing. In contrast, with `Stream.reduce`, every evaluation step has to return an object again, so `(x,y)->x+y` or `Integer::sum` will do the calculation with `int`, but then the result will be boxed to an `Integer` object. – Holger Jul 14 '17 at 13:18
  • @Holger no performance diff? there's an extra method in case of lambda expression that is generated, that would have a very small performance impact I think, unless inlined by jit – Eugene Jul 14 '17 at 13:31
  • @Eugene: no *extra* method, just a different method. It was an extra method, if you compared `(x,y) -> Integer.sum(x,y)` with `Integer::sum`. But when you write `(x,y) -> x+y`, it does exactly the same as `Integer::sum`. – Holger Jul 14 '17 at 13:33