Java8 Lambda performance vs public functions

I tested the lambda performance demo a bit using Java8 VS. Java8 public functions.

The point is this:

  • I have a list of 10 people (5 men and 5 women).

  • I would like to know which woman is between 18 and 25 years old.

Now, when I complete these steps during milion, the results are as follows:

Lambda with ForEach took: 395 ms (396 ms using JUnit)

Public functions took: 173 ms (169 ms using JUnit)

Lambda with Collect took: 334 ms (335 ms using JUnit)

Now I did not expect the lambda runtime to be two to six times longer than regular functions.

So now I’m pretty much wondering if I missed something here.

The source can be found here: pastebin.com/BJBk4Tu6


Following actions:

  • When expanding the list to 1,000,000 items
  • and filtering the whole young adult woman once

Results:

ForEach lambda took: 59 ms

Public features took: 15 ms

Lambda with the collection took: 12 ms

However, when I try to filter the same list of 1,000,000 people 100 times, the results are as follows:

Lambda with ForEach took: 227 ms

Public features took: 134 ms

Lambda with Collect took: 172 ms

So, as a final conclusion: Lambdas is faster when it comes to filtering large lists, while a public function (the old way) filters smaller lists faster.

In addition, public functions are faster when it comes to filtering multiple lists several times, for whatever purpose you require it.

Latest code: pastebin.com/LcVhgnYv

+6
source share
1 answer

As stated in the comments: you can hardly draw any conclusion from such a simple, simple and isolated launch of microbusiness.

Partially refers to another (otherwise unrelated) answer :

There are several options for correct and reliable runtime measurement. Besides a profiler like VisualVM , there are frameworks like JMH or Caliper , but admittedly using them can be a bit of an effort.

For the simplest form of a very simple manual Java Microbenchmark, you should consider the following:

  • Run the algorithms several times to give JIT the ability to hit
  • Run algorithms one at a time, and not just one after another
  • Running algorithms with increasing input size
  • Somehow save and print the calculation results to prevent optimization of the calculations.
  • Consider Timings May Be Distorted By The Garbage Collector (GC)

These are just rules of thumb , and there may still be unexpected results (see the links above for more details). But with this strategy, you usually get a good idea of ​​performance, and at least you can see if there are significant differences between the algorithms.

Related reading:

I applied these basic steps to your program. Here is the MCVE :

NOTE. The rest was updated in response to the subsequent editing of the question)

 import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Random; import java.util.stream.Collectors; class Person { public static final int MALE = 0; public static final int FEMALE = 1; private final String name; private final int sex; private final int age; public Person(String name, int sex, int age) { this.name = name; this.sex = sex; this.age = age; } public int getSex() { return sex; } public int getAge() { return age; } } public class Main { public static void main(String[] args) { new Main(); } private List<Person> people; public Main() { for (int size=10; size<=1000000; size*=10) { Random r = new Random(0); people = new ArrayList<Person>(); for (int i = 0; i < size; i++) { int s = r.nextInt(2); int a = 25 + r.nextInt(20); people.add(new Person("p" + i, s, a)); } int min = 10000000 / size; int max = 10 * min; for (int n = min; n <= max; n += min) { lambdaMethodUsingForEach(n); lambdaMethodUsingCollect(n); defaultMethod(n); } } } public void lambdaMethodUsingForEach(int n) { List<Person> lambdaOutput = new ArrayList<Person>(); long lambdaStart = System.currentTimeMillis(); for (int i = 0; i < n; i++) { lambdaOutput.addAll(getFemaleYoungAdultsUsingLambdaUsingForEach()); } System.out.printf("List size: %10d, runs: %10d, result: %10d, ForEach took: " + (System.currentTimeMillis() - lambdaStart) + " ms\n", people.size(), n, lambdaOutput.size()); } public void lambdaMethodUsingCollect(int n) { List<Person> lambdaOutput = new ArrayList<Person>(); long lambdaStart = System.currentTimeMillis(); for (int i = 0; i < n; i++) { lambdaOutput.addAll(getFemaleYoungAdultsUsingLambdaUsingCollect()); } System.out.printf("List size: %10d, runs: %10d, result: %10d, collect took: " + (System.currentTimeMillis() - lambdaStart) + " ms\n", people.size(), n, lambdaOutput.size()); } public void defaultMethod(int n) { List<Person> defaultOutput = new ArrayList<Person>(); long defaultStart = System.currentTimeMillis(); for (int i = 0; i < n; i++) { defaultOutput.addAll(getFemaleYoungAdultsUsingFunctions()); } System.out.printf("List size: %10d, runs: %10d, result: %10d, default took: " + (System.currentTimeMillis() - defaultStart) + " ms\n", people.size(), n, defaultOutput.size()); } public List<Person> getFemaleYoungAdultsUsingLambdaUsingForEach() { List<Person> people = new ArrayList<Person>(); this.people.stream().filter( (p) -> p.getSex() == Person.FEMALE && p.getAge() >= 18 && p.getAge() <= 25).forEach(people::add); return people; } public List<Person> getFemaleYoungAdultsUsingLambdaUsingCollect() { return this.people.stream().filter( (p) -> p.getSex() == Person.FEMALE && p.getAge() >= 18 && p.getAge() <= 25).collect(Collectors.toList()); } public List<Person> getFemaleYoungAdultsUsingFunctions() { List<Person> people = new ArrayList<Person>(); for (Person p : this.people) { if (p.getSex() == Person.FEMALE && p.getAge() >= 18 && p.getAge() <= 25) { people.add(p); } } return people; } } 

The output to My Machine® corresponds to the following lines:

  ... List size: 10, runs: 10000000, result: 10000000, ForEach took: 1482 ms List size: 10, runs: 10000000, result: 10000000, collect took: 2014 ms List size: 10, runs: 10000000, result: 10000000, default took: 1013 ms ... List size: 100, runs: 1000000, result: 3000000, ForEach took: 664 ms List size: 100, runs: 1000000, result: 3000000, collect took: 515 ms List size: 100, runs: 1000000, result: 3000000, default took: 441 ms ... List size: 1000, runs: 100000, result: 2300000, ForEach took: 778 ms List size: 1000, runs: 100000, result: 2300000, collect took: 721 ms List size: 1000, runs: 100000, result: 2300000, default took: 841 ms ... List size: 10000, runs: 10000, result: 2450000, ForEach took: 970 ms List size: 10000, runs: 10000, result: 2450000, collect took: 971 ms List size: 10000, runs: 10000, result: 2450000, default took: 1119 ms ... List size: 100000, runs: 1000, result: 2536000, ForEach took: 976 ms List size: 100000, runs: 1000, result: 2536000, collect took: 1057 ms List size: 100000, runs: 1000, result: 2536000, default took: 1109 ms ... List size: 1000000, runs: 100, result: 2488600, ForEach took: 1323 ms List size: 1000000, runs: 100, result: 2488600, collect took: 1305 ms List size: 1000000, runs: 100, result: 2488600, default took: 1422 ms 

You can see that the difference between ForEach and default approaches (public methods) disappears even for small lists. For larger lists, there seems to be a slight advantage for lambda oriented approaches.

To emphasize this again: this micro-tag is very simple , and even this does not necessarily indicate the performance of these approaches in practice. However, it’s at least reasonable to assume that the difference between ForEach and public methods is small, as suggested by your initial test. Nevertleless: +1 from me for those who run this in JMH or Caliper and post some additional information about it.

+9
source

Source: https://habr.com/ru/post/976407/


All Articles