48

I have quickly read over the Oracle Lambda Expression documentation.

This kind of example has helped me to understand better, though:

//Old way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
System.out.println(n);
}

//New way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));


//or we can use :: double colon operator in Java 8
list.forEach(System.out::println);

Still, I don't understand why it's such an innovation. It's just a method that dies when the "method variable" ends, right? Why should I use this instead of a real method? Which would be better option in terms of performance. Lambda or simple loop.

Ankur Mahajan
  • 3,396
  • 3
  • 31
  • 42
  • 29
    In terms of performance, your println takes about 1K * to 10K * the CPU of the rest of your code. If you eliminated the println, you would still find the fact the code hasn't warmed up could mean a 50x performance difference. If you warm up your code, you are likely to find the fact you used `List` instead of `int[]` your next biggest hit.... You are so far away from needing to know if the Stream or loop is faster. – Peter Lawrey Jul 28 '15 at 12:03
  • 4
    @PeterLawrey I agree that, if you want to do performance improvements, you have to look carefully in your code. And you even have to decide whether it's necessary it all. But as far as I understood the OP, it's not about this specific "example", but the overall difference between lambda and a loop. – 0lli.rocks Jul 28 '15 at 12:22
  • 5
    @Olli1511 good point, but I would say that for a wide range of example, the difference is a) unlikely to matter, b) likely to change depending on which update of Java 8 you are using. IMHO You should write the code you find clearest and worry about performance when you have measured that you have a problem. – Peter Lawrey Jul 28 '15 at 13:16
  • 1
    You may throw `IntStream.range(1,8).forEach(System.out::println);` in the ring as well. But you won’t notice a difference given such a small number of iterations and an operation like `println` which dominates execution time anyway. – Holger Jul 28 '15 at 13:32
  • 2
    You are asking the wrong question. Performance is so far down int he noise here as to be a total red herring. Use the approach that is more readable and maintainable. – Brian Goetz Jul 28 '15 at 14:14
  • 4
    @PeterLawrey I totally agree with your point. I am just thinking of someone who visits this question because of the headline. I would exspect an answer about the performance of these two apporaches. Event though, as you mentioned, for 99,9% of the visitors, it doesn't make sense to think about the performance difference, but what if it **does** matters in 0,01% (i can't think of a scenario, but there might be one)?. Shouldn't those visitors get an answer to their question and not only "don't care about it"? – 0lli.rocks Jul 28 '15 at 14:23
  • 1
    Has anyone studied the bytecode output of the samples provided by the OP? Are they the same instructions, or are there some shortcuts that the different expressions provide? – Paul Williams Jul 28 '15 at 16:18
  • 5
    @Olli1511 There isn't a broad answer which is correct in all cases. The answer is it depends. I have seen cases where using a lambda was slower and where it was faster. I have also seen that with newer updates you get more optimal code. – Peter Lawrey Jul 28 '15 at 17:08
  • 1
    True, whether or not a lambda or a simple loop will *actually* be faster in your application depends on many (many) factors The obvious ones, (disregarding artificial micorbenchmarks etc) are: What is *done* in the loop? And how large is the list? Any general statement here would be an oversimplification. So paraphrasing and actually [quoting](http://www.oracle.com/technetwork/articles/java/devinsight-1-139780.html) @BrianGoetz: Write Dumb Code :) – Marco13 Jul 28 '15 at 19:05
  • 1
    @PaulWilliams, of course bytecode is very different, but it's irrelevant. The JIT-compiled code differs very much from Java bytecode. – Tagir Valeev Jul 29 '15 at 04:08

9 Answers9

57

My advice would be:

  1. Use the style that you and your coworkers agree is most maintainable.

  2. If you and your colleagues are not yet comfortable with lambdas, keep learning.

  3. Don't obsess over performance. It is often not the most important thing.

Generally speaking, lambdas and streams provide a more concise and (once everyone is up to speed) more readable way of expressing this kind of algorithm. Performance is not the primary goal.

If performance does become an issue, then the standard advice is to code, test, benchmark, profile and optimize. And do it in that order! You can easily waste a lot time by optimizing at the coding stage, or by optimizing code that has minimal impact on overall application performance.

  • Let the application benchmarks tell you if you need to optimize at all.
  • Let the profiler point out the parts of your code that are worthy of the effort of optimization.

In this specific example, the performance difference is going to be too small to measure. And if you scaled up to a list of millions of elements, the performance will be dominated by the time taken to build the list and write the numbers. The different ways of iteration will only contribute a small part to the overall performance.


And for folks, who (despite all of the above) still want to know whether it is faster to use a lambda or a conventional loop, the best general answer is:

"It depends on all sorts of factors that 1) are not well understood, and 2) liable to change as Java compiler technology evolves.

We could give you an answer for a specific example with a specific Java major/minor/patch release, but it would be unwise to generalize.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
11

Why should I use this instead of a real method?

You should not. Use the approach which you like more.

As for performance, I guess, all these versions are roughly equally fast. Here I/O operation (println) is much slower than all possible overhead of calling lambda or creating an iterator. In general forEach might be slightly faster as it does everything inside the single method without creating the Iterator and calling hasNext and next (which is implicitly done by for-each loop). Anyway, this depends on many factors, such as how often you call this code, how long your list is, whether JIT compiler managed to devirtualize the iterator and/or lambda, and so on.

royhowie
  • 11,075
  • 14
  • 50
  • 67
Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
5

It allows you write in one line (while having enough readability) something, what was not possible before. Performance is not issue here O(n) stays O(n).

For example in my Windows Phone App, I have sessions and in that sessions are performers and I want to select all sessions which have one concrete performer (like you want to see all the movies some actor plays in). In Java 1.7 or less I had to create loop with inner loop, checking for it, returning null if there is no performer etc. And with lambda expressions, I can do this :

//performerId is parameter passed by method before
Sessions.Where(x => x.Performers.Where(y => y.PerformerId == performerId).FirstOrDefault() != null)

It is same in Java now (however I am not working on 1.8 project right now, I do not have Java example, but I am looking forward to it).

libik
  • 22,239
  • 9
  • 44
  • 87
3

If you want to understand the value of lambda expressions you shouldn’t look at the only new method which has a language counterpart which existed before. How about these examples:

button.addActionListener( ev -> BACKGROUND_EXECUTOR_SERVICE.execute( () -> {
   String result = longComputation();
   SwingUtilities.invokeLater( () -> label.setText(result) );
});

Just think about how the equivalent pre-Java 8 code looks like and you see that the main advantage is not performance here.

If you want to look at Collection operations, how about this one?

map.merge(key, 1, Integer::sum);

This will put 1 into the map, if the key doesn’t exist in the map yet or add 1 to the value of the existing mapping otherwise. Again, think about how the old counterpart looks like. The fact that it might be even more efficient as it avoids multiple hash operations, if the map has an appropriate implementation of the method, is only an additional benefit.

Surely, the forEach method can not provide such a big gain in expressiveness as there is the for-each language counterpart. Still, not needing to declare a loop variable can improve the source code, if the declaration requires to repeat a long type name and generic parameters. That’s especially true in the context of Maps:

Map<ContainerOrderFocusTraversalPolicy, List<AttributedCharacterIterator>> map;
//…

 

map.forEach((p,l)->l.forEach(i->{ /* do your work using p and i */}));

here, this new iteration clearly wins over

for(Map.Entry<ContainerOrderFocusTraversalPolicy, List<AttributedCharacterIterator>> e:
                                                                      map.entrySet()) {
    ContainerOrderFocusTraversalPolicy p=e.getKey();
    for(AttributedCharacterIterator i: e.getValue()) {
        /* do your work using p and i */
    }
}

Of course, it only matters if the actual work statements are small enough but that’s how lambda expressions should be used: to encapsulate small pieces of code. And there are still tasks which can’t be done this way and require an ordinary for-each loop, just as there are also tasks which can’t be done with a for-each loop and need the even-older for loop dealing with an Iterator manually…

Holger
  • 285,553
  • 42
  • 434
  • 765
2

In terms of performance a normal function will be better as compare to lambda because in groovy there are closures present which is more or like same as lambda.

These things are working in a way like if you write a closure for any collection it will internally create a another class which actually does action for mentioned closure or lambda expression.

But, by using lambda and closure i can iterate things in better way as well as i can debug easily. You can write less line of codes.

Romano Zumbé
  • 7,893
  • 4
  • 33
  • 55
Ashay Jain
  • 98
  • 6
0

When using lambda in your code, you are telling what to do not how to do it. Above code passing a method reference instead of looping over the list, I think the lambda one is more ergonomic.

As far as performance is concerned, printing of a list of 1 million items took almost same time but I have not bench-marked other kind of operation.

The above code is a trivial operation but lambda has quite a few advantages as you can have functions which you can pass around, using multiple core(parallel streams) is easy etc.

barunsthakur
  • 1,196
  • 1
  • 7
  • 18
0
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Timeout;
import org.openjdk.jmh.annotations.Warmup;

/**
 *
 * @author devb
 */
@BenchmarkMode(Mode.Throughput)
@Fork(value = 1)
@Warmup(iterations = 1, time = 32, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 16, time = 1, timeUnit = TimeUnit.SECONDS)
@Timeout(time = 1, timeUnit = TimeUnit.SECONDS)
@State(Scope.Benchmark)
public class CheckLamdaPerformance {

    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

    @Benchmark
    public void testPerOld() {
        //Old way:
        for (Integer n : list) {
            System.out.println(n);
        }
    }

    @Benchmark
    public void testPerNew() {
        //New way:
        list.forEach(n -> System.out.println(n));
    }

    @Benchmark
    public void testPerDoubleColon() {
        //or we can use :: double colon operator in Java 8
        list.forEach(System.out::println);
    }

}

To Test benchmark

import java.io.IOException;
import org.openjdk.jmh.Main;
import org.openjdk.jmh.runner.RunnerException;

/**
 *
 * @author devb
 */
public class MyBenchmark {

    private static final String TEST = ".*CheckLamdaPerformance.*"; 

    public static void main(String[] args) throws IOException, RunnerException {
        Main.main(getArguments(TEST, 1, 5000, 1));
    }

    private static String[] getArguments(String className, int nRuns, int runForMilliseconds, int nThreads) {
        return new String[]{className,
            "-i", "" + nRuns,
            "-r", runForMilliseconds + "ms",
            "-t", "" + nThreads,
            "-w", "5000ms",
            "-wi", "1"
        };
    }

}

After Running Output is:

# Run complete. Total time: 00:00:34

Benchmark                                  Mode  Cnt     Score   Error  Units
CheckLamdaPerformance.testPerDoubleColon  thrpt        776.008          ops/s
CheckLamdaPerformance.testPerNew          thrpt       1096.423          ops/s
CheckLamdaPerformance.testPerOld          thrpt        873.542          ops/s
Bhuwan Prasad Upadhyay
  • 2,916
  • 1
  • 29
  • 33
  • 6
    If you ran this test without writing to the console it would be micro-seconds or less. I would also ignore the first 10,000 times the code is run as it hasn't warmed up yet. – Peter Lawrey Jul 28 '15 at 12:05
  • @TagirValeev, I have tried it and i am surprised. How should I explain that phenomenon. – Kachna Jul 28 '15 at 12:25
  • 1
    Much better! Still, can you provide some justification for the jmh tuning parameters you have provided? Most notably the single iteration warmup? I would remove all those tuning option until you know what you are doing. – Boris the Spider Jul 29 '15 at 06:11
  • Tuning parameters description see in http://jmhwiki.weebly.com/blog/java-micro-benchmarking – Bhuwan Prasad Upadhyay Jul 29 '15 at 06:40
  • 1
    @developerbhuwan i know fully well what those options do! I am wondering why you chose those values - to me they seem way off and I would have left it at the default unless I had good reason to suspect that jmh wasn't performing correctly. Why did you choose those values? – Boris the Spider Jul 29 '15 at 09:17
0

Look at first part in Guava documentation. It is about older Java version but makes important point - using lambdas everywhere might actually make code less readable. Better example might be the one from Stream API:

 // Group employees by department
 Map<Department, List<Employee>> byDept
     = employees.stream()
                .collect(Collectors.groupingBy(Employee::getDepartment));
user158037
  • 2,659
  • 1
  • 24
  • 27
0

The run-time and readability certainly seems to vary case-by-case, but in my mind there is a pretty good answer to

Why should I use this instead of a real method?

You can pass a lambda function as a variable
Correct me if I'm off base on this, as I've been mostly a Fantom user for a while now, but you can pass a Lambda function as an argument to a method call. For example, if you have a sort method built, it could take a lambda argument as the comparator to use, allowing you to use the single sort method to compare different fields in an object, or even completely different object, easily.

Cain
  • 264
  • 1
  • 7