2

Which one is faster? Adding to list in foreach or mapping&collecting in stream.

//Solution 1
List<Card> cards = mobileUserService.getCurrentUser().getUserCard();

final List<CardDTO> dtos = new ArrayList<>(cards.size());

cards.forEach(card -> dtos.add(cardTransformer.transform(card)));

or

//Solution 2
List<Card> cards = mobileUserService.getCurrentUser().getUserCard();

List<CardDTO> dtos = cards.stream()
        .map(cardTransformer::transform)
        .collect(Collectors.toList());

Benchmark:

In most cases foreach seems be faster.

Test code variant 1:

   List<Card> cards = new ArrayList<>();
       for (int i = 0; i < count; i++) {
        Card c = Card.createFakeCard();
        cards.add(c);
    }


    long startTime1 = System.nanoTime();
    //Solution 1
    final List<CardDTO> dtos = new ArrayList<>(cards.size());
    cards.forEach(card -> dtos.add(cardTransformer.transform(card)));
    long endTime1 = System.nanoTime();


    long startTime2 = System.nanoTime();
    //Solution 2
    List<CardDTO> dtos2 = cards.stream()
            .map(cardTransformer::transform)
            .collect(Collectors.toList());

    long endTime2 = System.nanoTime();


    double runtime1 = (endTime1 - startTime1) / Math.pow(10, 6);
    double runtime2 = (endTime2 - startTime2) / Math.pow(10, 6);
    log.error("Number of elements: " + count + "\n" +
            "Solution 1 " +
            "Total time (ms): " + runtime1 + "\n" +
            "Solution 2 " +
            "Total time (ms): " + runtime2);

Results variant 1:

Number of elements: 1
Solution 1 Total time (ms): 3.862259
Solution 2 Total time (ms): 8.919641
Number of elements: 1
Solution 1 Total time (ms): 0.012556
Solution 2 Total time (ms): 0.032712
Number of elements: 1
Solution 1 Total time (ms): 0.011565
Solution 2 Total time (ms): 0.034363
Number of elements: 2
Solution 1 Total time (ms): 0.01619
Solution 2 Total time (ms): 0.03965
Number of elements: 10
Solution 1 Total time (ms): 0.020486
Solution 2 Total time (ms): 0.044607
Number of elements: 100
Solution 1 Total time (ms): 0.395842
Solution 2 Total time (ms): 0.729233
Number of elements: 1000
Solution 1 Total time (ms): 0.276229
Solution 2 Total time (ms): 0.37866
Number of elements: 5000
Solution 1 Total time (ms): 0.987951
Solution 2 Total time (ms): 1.092693
Number of elements: 10000
Solution 1 Total time (ms): 2.701169
Solution 2 Total time (ms): 3.287001
Number of elements: 20000
Solution 1 Total time (ms): 11.095115
Solution 2 Total time (ms): 11.3046
Number of elements: 50000
Solution 1 Total time (ms): 4.339383
Solution 2 Total time (ms): 6.235984
Number of elements: 100000
Solution 1 Total time (ms): 8.312332
Solution 2 Total time (ms): 9.088485

Test code variant 2:

   List<Card> cards = new ArrayList<>();
       for (int i = 0; i < count; i++) {
        Card c = Card.createFakeCard();
        cards.add(c);
    }



    long startTime2 = System.nanoTime();
    //Solution 2
    List<CardDTO> dtos2 = cards.stream()
            .map(cardTransformer::transform)
            .collect(Collectors.toList());

    long endTime2 = System.nanoTime();


    long startTime1 = System.nanoTime();
    //Solution 1
    final List<CardDTO> dtos = new ArrayList<>(cards.size());
    cards.forEach(card -> dtos.add(cardTransformer.transform(card)));
    long endTime1 = System.nanoTime();

    double runtime1 = (endTime1 - startTime1) / Math.pow(10, 6);
    double runtime2 = (endTime2 - startTime2) / Math.pow(10, 6);
    log.error("Number of elements: " + count + "\n" +
            "Solution 1 " +
            "Total time (ms): " + runtime1 + "\n" +
            "Solution 2 " +
            "Total time (ms): " + runtime2);

Results variant 2:

Number of elements: 1
Solution 1 Total time (ms): 1.672247
Solution 2 Total time (ms): 9.868603
Number of elements: 1
Solution 1 Total time (ms): 0.005617
Solution 2 Total time (ms): 0.043946
Number of elements: 1
Solution 1 Total time (ms): 0.005618
Solution 2 Total time (ms): 0.040971
Number of elements: 2
Solution 1 Total time (ms): 0.006278
Solution 2 Total time (ms): 0.041963
Number of elements: 10
Solution 1 Total time (ms): 0.011564
Solution 2 Total time (ms): 0.045929
Number of elements: 100
Solution 1 Total time (ms): 0.065093
Solution 2 Total time (ms): 0.121263
Number of elements: 1000
Solution 1 Total time (ms): 0.65555
Solution 2 Total time (ms): 0.968456
Number of elements: 5000
Solution 1 Total time (ms): 0.779127
Solution 2 Total time (ms): 1.244686
Number of elements: 10000
Solution 1 Total time (ms): 2.03769
Solution 2 Total time (ms): 2.337048
Number of elements: 20000
Solution 1 Total time (ms): 6.12232
Solution 2 Total time (ms): 6.038063
Number of elements: 50000
Solution 1 Total time (ms): 6.12232
Solution 2 Total time (ms): 8.463334
Number of elements: 100000
Solution 1 Total time (ms): 16.468047
Solution 2 Total time (ms): 17.86109
Kriczer
  • 497
  • 2
  • 12
  • 6
    What did your benchmarks say? – Fildor Feb 21 '17 at 11:03
  • 4
    For being more declarative you can replace `map(card -> cardTransformer.transform(card))` with `map(cardTransformer::transform)` – Andrii Abramov Feb 21 '17 at 11:03
  • Do you think there is a significant difference? If so, why? – Kayaman Feb 21 '17 at 11:34
  • @AndriiAbramov fixed :) – Kriczer Feb 21 '17 at 11:34
  • @Fildor Benchmark was added in last edit. – Kriczer Feb 21 '17 at 12:14
  • 2
    Possible duplicate of [How do I write a correct micro-benchmark in Java?](http://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java) – Eugene Feb 21 '17 at 14:03
  • 1
    The problem with benchmarks is that they always give an answer, but the answer doesn't necessarily mean anything. Benchmarks like these (run it a few times and measure with `nanoTime()`) are generally worthless. But the bottom line is, stop worrying about micro-performance issues (which these are), and apply all those brain-cycles instead to writing clear, maintainable code. You'll get a far better return on that investment. – Brian Goetz Feb 22 '17 at 17:09

1 Answers1

0

I'm not an expert on how the JVM handles the lambdas but I did some research on my own some months ago, so... I think the key is the number of "lambdas" that are in your stream.

Every time you write a lambda, 2 main things will happen:

  1. At compilation time, the lambda will be extracted as a private method of the host class. You can actually see that using javap
  2. At runtime, the JVM will create an object to call that private method. (There is a lot of more fun here but for simplicity's sake)

Then in your code, the first case has only 1 lambda, but the second has 2. Therefore at some point, the JVM will have to wire, create and instantiate an object for your second case, 2 times. That is what your first code is "faster".

xiumeteo
  • 941
  • 7
  • 16