Статьи

Сортировка Java8 — ловушка производительности

Java 8 обладает всеми преимуществами лямбд, чтобы мы могли программировать, используя декларативный стиль. Но действительно ли это бесплатно? И стоит ли нам беспокоиться о цене, которую мы должны заплатить за новые программные вкусности?

Вот пример, где мы могли бы беспокоиться.

Рассмотрим сортировку экземпляров этого простого класса:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
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; }
}

Мы можем отсортировать, используя новый декларативный синтаксис Java 8, как показано ниже:

1
2
3
4
5
6
7
8
List<MyComparableInt> mySortedComparableList =
      myComparableList.stream()
       .sorted(Comparator.comparing(
         MyComparableInt::getA).thenComparing(
         MyComparableInt::getB).thenComparing(
         MyComparableInt::getC).thenComparing(
         MyComparableInt::getD))
       .collect(Collectors.toList());

Или мы можем отсортировать старый способ (все еще используя лямбды), используя этот код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;
        }
 }

Когда я запустил этот тест с 10-метровыми объектами, сортировка заняла ~ 6,5 с, используя декларативный синтаксис, но только 1,5 с, используя старый синтаксис. Это почти в 4 раза больше!

Так, где время идет? Предположительно, это накладные расходы на сортировку между методами thenComparing.

Интересно, что если вы попробуете точно такой же тест, но замените int на String, времена изменятся следующим образом. Опять же для 10-метровых объектов использование нового синтаксиса занимает ~ 11,5 с, в то время как старый синтаксис занимает ~ 7 с. Используя String, когда маршаллинг менее значим, новый синтаксис занимает всего 1,5 раза дольше старого синтаксиса.

Таким образом, хотя новый синтаксис выглядит великолепно и удивительно выразительно, если вы беспокоитесь о производительности, вам следует придерживаться старого синтаксиса.

Еще раз кажется, что нет такой вещи как бесплатный обед!