5

Out of curiosity if using Comparator in java8 style i.e. using Lambda expressions has any advantage over regular comparison i.e.

One method of sorting by id is:-

List sortedAcs = ac
.stream()
.sorted((a, b) -> Long.compare(a.getId(), b.getId())) //sorted by id here
.collect(Collectors.toList());

Other methods could be Java 8 way:-

List sortedAcs = ac
.stream()
.sorted(Comparator.comparingLong(AC::getId)) //sorted by id here
.collect(Collectors.toList());

Is there any performance benefit in later approach (java-8 method reference) over former approach?

Please help!!!

Vinay Prajapati
  • 7,199
  • 9
  • 45
  • 86
  • Any difference will be negligible. They both do the same thing. – cpp beginner Oct 25 '17 at 05:04
  • inside 'java.util.Comparator.comparingLong' method you can see the same thing done as first example '(c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2))' – janith1024 Oct 25 '17 at 05:04
  • 4
    Engineers love to measure the things. Could you please try measuring the performance by doing it 100K times? It would be great to see your findings in the answer section. – Yogi Devendra Oct 25 '17 at 05:08
  • whoever downvoted please let me know the reason for that. Is the question not relevant for the portal? – Vinay Prajapati Oct 25 '17 at 05:14
  • 2
    Any performance differences between the single comparisons are irrelevant, the biggest performance hit is caused by the actual sorting. If you're trying to choose one over the other on performance basis, then you don't really understand performance tuning (or sorting). – Kayaman Oct 25 '17 at 05:46
  • there is a huge performance difference coming up so far the code i am trying with. Its almost 50 ms slower with comparingLong. I ran the code for 1000 time and the difference on average comes out to be this. – Vinay Prajapati Oct 25 '17 at 05:56
  • 6
    The benefit of the second variant is not performance, but simplification. It’s shorter and it avoids code duplication. It’s more than a theoretical point; I’ve seen copy-and-paste errors even in such short code snippets, e.g. `(a, b) -> Long.compare(a.getId(), a.getId())` and you have to look twice to spot the error. If you want this specific snippet to be faster, use `List sortedAcs=new ArrayList<>(ac); sortedAcs.sort(Comparator.comparingLong(AC::getId));` to sort in-place. – Holger Oct 25 '17 at 08:34
  • @VinayPrajapati If you didn't use JMH, your results are invalid – erickson Oct 26 '17 at 04:08
  • @erickson i didn't use JMH. Will try to use it and put my comment here. – Vinay Prajapati Oct 26 '17 at 04:16
  • @erickson put my answer below for my findings... – Vinay Prajapati Oct 26 '17 at 05:59

4 Answers4

9

The only thing that is different is the number of methods it would take to accomplish what you want. Comparator.comparingLong will apply a ToLongFunction for each argument and then delegate to Long.compare. But this is a simple optimization that JIT should take care of. I'd say that because of this the difference, there could be a small difference (until JIT kicks in), but it would be so small that is absolutely neglectable and should not drive your decision in any way.

On the other hand, if you actually see any differences, than it's probably your testing code that is the issue, not the code that is being measured.

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • so basically there would be a performance hit even if it is minor in case of lambda expressions. – Vinay Prajapati Oct 25 '17 at 09:17
  • 1
    @VinayPrajapati both examples are using lambdas. The other one just uses method references. – Kayaman Oct 25 '17 at 09:20
  • yeah! my bad. I meant for using method references – Vinay Prajapati Oct 25 '17 at 09:21
  • 1
    @VinayPrajapati There is a difference between lambdas and method references, but whether it has any significant impact is doubtful. See https://softwareengineering.stackexchange.com/questions/277473/is-there-a-performance-benefit-to-using-the-method-reference-syntax-instead-of-l – Kayaman Oct 25 '17 at 09:27
2

Any performence difference in the two code snippets will be negligable. And if you really need to optimize that code, then not using streams will probably give you a much greater performance boost than replacing that comparator.

The only criterion you should use to choose between the two variants is clarity: which one do you think conveys the intent of the code more clearly? In the end, this is a personal preference, depending on how fluent you are with the Java 8 features, among other things.

Personally, I find the second snippet more clear than the first one. The comparingLong method (and other comparingX methods) immediately tells me: here we're comparing objects based on the value of a (long-typed) attribute. In the first snippet, I first need parse the code to determine that that is indeed what happens.

Hoopje
  • 12,677
  • 8
  • 34
  • 50
1

So, here comes answer from one perspective i.e. performance.

Here is my code I used to test it:-

AC class:-

package com.test;

public class AC {

    private Long id;

    public AC(Long id) {
        this.id = id;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "AC{" +
                "id=" + id +
                '}';
    }
}

Main class:-

package test.java;

import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class Main {

    @org.openjdk.jmh.annotations.Benchmark
    public void measureName() {
        List<AC> acs = new ArrayList<>();

        acs.add(new AC(20l));
        acs.add(new AC(30l));
        acs.add(new AC(10l));
        acs.add(new AC(30l));
        acs.add(new AC(80l));
        acs.add(new AC(50l));
        acs.add(new AC(30l));
        acs.add(new AC(90l));
        acs.add(new AC(80l));
        acs.add(new AC(110l));

   /*     acs
                .stream()
                .sorted(Comparator.comparingLong(AC::getId)) //sorted by id here
                .collect(Collectors.toList());*/

        acs.stream()
                .sorted((a, b) -> Long.compare(a.getId(), b.getId())) //sorted by id here
                .collect(Collectors.toList());

    }

    public static void main(String[] args) {

        Options opt = new OptionsBuilder()
                .include(".*" + Main.class.getSimpleName() + ".*")
                .forks(1)
                .build();

        try {
            new Runner(opt).run();
        } catch (RunnerException e) {
            e.printStackTrace();
        }
    }
}

Putting below output using JMH for using Comparator.comparingLong:-

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

Benchmark          Mode  Cnt        Score       Error  Units
Main.measureName  thrpt   20  4130836.283 ± 86675.431  ops/s

and for Long.compare below:-

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

Benchmark          Mode  Cnt        Score        Error  Units
Main.measureName  thrpt   20  4106542.318 ± 146956.814  ops/s

If I go by these statistics Long.compare is somehow faster though difference is very minor.

Please feel free to put in comments your findings if any and I would try those too.

Vinay Prajapati
  • 7,199
  • 9
  • 45
  • 86
  • If I calculate it, it's almost 24ms difference in general. But looks like the uncertainty is more with `Long.compare`. – Adesh Kumar Oct 26 '17 at 09:39
  • 2
    These results show that *`comparingLong()`* is slightly faster—it has higher throughput, but only about 0.6%. However, `comparingLong()` is more readable, and since it has as good or better performance, it is the clear choice to use. – erickson Oct 26 '17 at 13:49
0

I was trying to sort a Map before having gotting a warning about the usage of

(entry1,entry2)->Long.compare(entry1.getKey(),entry2.getKey())

which should be replaced using Comparator.comparingLong and the result is really a better way to implement it:

Comparator.comparingLong(Map.Entry::getKey)

Inspecting the implementation of comparingLong i found out that it is basically the same implementation, only that it is cleaner and less time consuming.

return (Comparator<T> & Serializable)
(c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2))
velocity
  • 1,630
  • 21
  • 24