Java8 Sorting – Performance Pitfall
Java 8 brings all the goodness of lambdas to enable us to program using a declarative style. But is it really free? And should we be concerned about the price we have to pay for the new programming goodies?
Here’s an example where we might have to worry.
Consider sorting instances of this simple class:
private static class MyComparableInt{ private int a,b,c,d; public MyComparableInt(int i) { a = i%2; b = i%10; c = i%1000; d = i; } public int getA() { return a; } public int getB() { return b; } public int getC() { return c; } public int getD() { return d; } }
We can sort using the new Java 8 declarative syntax as below:
List<MyComparableInt> mySortedComparableList = myComparableList.stream() .sorted(Comparator.comparing( MyComparableInt::getA).thenComparing( MyComparableInt::getB).thenComparing( MyComparableInt::getC).thenComparing( MyComparableInt::getD)) .collect(Collectors.toList());
Or we can sort the old way (still using lambdas) using this code:
List<MyComparableInt> mySortedComparableList = myComparableList.stream() .sorted(MyComparableIntSorter.INSTANCE) .collect(Collectors.toList()); public enum MyComparableIntSorter implements Comparator<MyComparableInt>{ INSTANCE; @Override public int compare(MyComparableInt o1, MyComparableInt o2) { int comp = Integer.compare(o1.getA(), o2.getA()); if(comp==0){ comp = Integer.compare(o1.getB(), o2.getB()); if(comp==0){ comp = Integer.compare(o1.getC(), o2.getC()); if(comp==0){ comp = Integer.compare(o1.getD(), o2.getD()); } } } return comp; } }
When I ran this test with 10m objects the sort took ~6.5s using the declarative syntax but only 1.5s using the old syntax. That’s almost 4 times as long!
So where is the time going? Presumably it’s in the overhead for marshalling between the thenComparing methods.
Interestingly, if you try exactly the same test but replace int for String the times change as follows. Again for 10m objects, using the new syntax takes ~11.5s whilst the old syntax takes ~7s. Using String, when the marshalling is less significant the new syntax takes only 1.5 times as long as the old syntax.
In summary, whilst the new syntax looks great and is wonderfully expressive, if you are worried about performance you should stick to the old syntax.
Once again it seems that there is no such thing as a free lunch!
Reference: | Java8 Sorting – Performance Pitfall from our JCG partner Daniel Shaya at the Rational Java blog. |
How about…?
List mySortedComparableList =
myComparableList.stream()
.sorted(Comparator.comparingInt(
MyComparableInt::getA).thenComparingInt(
MyComparableInt::getB).thenComparingInt(
MyComparableInt::getC).thenComparingInt(
MyComparableInt::getD))
.collect(Collectors.toList());
comparingInt/thenComparingInt seems to be a bit faster than comparing/thenComparing version.
However it’s still much slower than Comparable/Comparator approach.
http://pastebin.com/neV0vVfE
Good stuff.
This doesn’t go for or against your point, but I just wanted to point out that the benefits of the first extend beyond more than readability and ease of use. What is probably the biggest benefit is the reuse factor. If you composed your Comparator from several small comparators, you could mix, match, and rearrange them as you please for other comparisons in other places.
This, of course, does not make your point wrong. It’s just another thing to consider when choosing how you want to do it.
what if you replace int with the wrapper type Integer?