Why is there such a difference in performance between stream and normal loops?

Asked

Viewed 596 times

16

I was reading a article related to the performance of streams/loops, and was startled by the difference in performance between the use of loops for large quantities of data.

I decided to perform a quick test using the following code:

public static void main(String[] args) {
    final int limite = 10_000;

    long inicioFor = System.currentTimeMillis();

    for(int i = 0; i < limite; i++) {
        System.out.println(i);
    }

    long terminoFor = System.currentTimeMillis();

    long inicioStream = System.currentTimeMillis();

    IntStream.range(0, limite).forEach(System.out::println);

    long terminoStream = System.currentTimeMillis();

    System.out.println();
    System.out.println("Usando for: " + (terminoFor - inicioFor) );
    System.out.println("Usando stream: " + (terminoStream - inicioStream) );
}

It’s a relatively simple code, but I noticed something interesting:

  1. When the limite is defined with value 10_000, the resulting time is as follows:

Using for: 54
Using stream: 72

  1. When the limite is defined with value 1_000_000, to stream is relatively faster:

Using for: 4314
Using stream: 4202

I believe that this is a simple test without the use of benchmark among other metrics.

I would like to know why there is this difference in processing time using streams and loops, may not be correct, but it seems that streams perform better the higher the amount of data (walking to the issue of streams infinite).


Complementing with some information posted by @Victor, I had extremely different results withdrawing IO operations:

  1. When the limite is defined with value 10_000:

Using for: 0
Using stream: 36

  1. When the limite is defined with value 1_000_000:

Using for: 2
Using stream: 43

As also pointed out by @Maniero, the reason should be the high cost to make the infrastructure work accordingly.

2 answers

10

Well, I took that test:

import java.util.stream.LongStream;

class Teste {
    public static void main(String[] args) {
        var limite = 50_000_000_000L;
        var x = new long[1];

        var inicioStream1 = System.currentTimeMillis();
        LongStream.range(0, limite).forEach(z -> x[0] = z);
        var terminoStream1 = System.currentTimeMillis();

        var inicioFor1 = System.currentTimeMillis();
        for (var i = 0L; i < limite; i++) {
            x[0] = i;
        }
        var terminoFor1 = System.currentTimeMillis();

        var inicioStream2 = System.currentTimeMillis();
        LongStream.range(0, limite).forEach(z -> x[0] = z);
        var terminoStream2 = System.currentTimeMillis();

        var inicioFor2 = System.currentTimeMillis();
        for (var i = 0L; i < limite; i++) {
            x[0] = i;
        }
        var terminoFor2 = System.currentTimeMillis();

        System.out.println();
        System.out.println("Usando for: " + (terminoFor1 - inicioFor1));
        System.out.println("Usando stream: " + (terminoStream1 - inicioStream1));
        System.out.println("Usando for outra vez: " + (terminoFor2 - inicioFor2));
        System.out.println("Usando stream outra vez: " + (terminoStream2 - inicioStream2));
    }
}

The result was this:

Usando for: 15169
Usando stream: 18422
Usando for outra vez: 15140
Usando stream outra vez: 18498

When you go to make a bechmark of something, remember that the IO for the console is a costly operation. This means that you should never use System.out.println or System.out::println within the code you are evaluating, because it will greatly interfere with the performance of what you are evaluating.

Believe that the System.out.println has a zero or negligible performance cost is a common mistake.

  • 1

    In fact the IO was affecting the result, testing the same example by removing the IO operation was clear even in my example that the stream is actually slower :)

  • Was it only 20% difference in performance? I expected something more glaring

9


I just answered about this (The LINQ is the stream of C#, roughly). The stream is an abstraction, is an extra layer to run, there is a cost to make this infrastructure work, there are function calls where it should not occur in pure, there are indirect, All this has cost, so it needs to be more expensive than the pure and simple execution of a loop. It is running more things, it has to be slower.

The stream is a method that has a loop inside it and that executes a method within it indirectly, in this example is the System.out::println. Which by the way is not a good way because it’s IO and the cost of it is so high that you’re measuring something like 98% of the time the IO and not the loop or the mechanism of the stream. In something that costs little the difference is much greater.

It impresses me the excellent optimization that the Java compiler does, but the test where gives the for slower certainly caught some interference, there’s no way it’s slower. Certainly in large volume the cost of the infrastructure may not impact so much, but it cannot be faster. And why it gives a discrepancy between its result and Victor’s, his often performs that diminishes the influence of the stream.

  • There is something more bizarre going on. Even doing bechmark without putting System.out inside the loop or Stream, still the result is strange.

  • @Victorstafusa Have the code? I can try to explain the reason if you see it. It is not only the indirect call that causes the slowness.

  • var x = new int[1];, IntStream.range(0, limite).forEach(z -> x[0] = z);, x[0] = i;.

  • 1

    @Victorstafuses this example is better because it takes out the IO that is slow. What is the proportion that got?

  • I posted an answer.

  • I’ll see, but I don’t understand what you find most bizarre.

  • 2

    It was my mistake while I was coding.

Show 2 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.