Java 8 Lambda vs LambdaJ vs Guava vs Iterative approach

в 5:45, , рубрики: guava, java, java 8, jdk8, lambda, метки: , , , ,

В середине 2013 года выходит Java 8 с реализацией лямбда-выражений. Лямбда-выражения предоставляют широкие возможности для применения функционального стиля программирования. Правда функциональный стиль можно использовать уже сегодня в JDK 7, 6 или 5 с помощью библиотек LambdaJ и Guava.

Iterative Lambdaj JDK 8 lambda Guava
Print all brands 79 472* 113 79
Select all sales of a Ferrari 25 146 44 31
Find buys of youngest person 1,209 1,775 1,242 1,218
Find most costly sale 8 123 55 72
Sum costs where both are males 23 973* 40 45
Age of youngest who bought for > 50,000 1,521 2,576* 1,560 1,511
Sort sales by cost 350 1,187 473 453
Extract cars original cost 29 61 31 31
Index cars by brand 57 70 89 415
Group sales by buyers and sellers 2,586 3,748* 2,862 1,601
Find most bought car 744 1,023* CRASH 1,435


Guava — достаточно обширная библиотека, но её ядро составляют классы для работы с коллекциями, собственно библиотека и выросла из google-collections. Эти классы реализуют всё то для работы с коллекциями, чего не хватает в JDK на сегодняшний день. Если приглядеться к JDK 8, то постоянно возникает ощущение, что та или иная новая возможность уже есть в guava. Не исключено, что разработчики восьмой джавы опирались именно на опыт гуавы.

LamdaJ, несмотря на свое название, не предоставляет возможности писать лямбда-выражения, это скорее библиотека для работы с коллекциями. Она дает возможности фильтровать, конвертировать, группировать элементы коллекций, при этом код получается очень компактный и хорошо читается. Она реализована средствами reflection и сglib, и очевидно, что за удобство приходится платить производительностью. О LambdaJ уже писали на хабре.

В JDK 8 лямбда-выражения будут реализованы уже на уровне компилятора и будут преобразовываться в байткод еще на этапе компиляции, поэтому теоретически производительность такого кода должна быть выше.

Сравнение производительности

Для того чтобы практически сравнить производительность лямбда-выражений из JDK 8 с LambdaJ, Guava и обычным итеративным подходом, были взяты тесты производительности LambdaJ и написаны аналоги на JDK 8 и Guava.

Изначальный код тестов для итеративного варианта и LambdaJ тронут не был, названия тестов сохранены на английском во избежание путаницы. Методика тестирования следующая: тесты запускаются в 100 проходов, где каждый тест запускается 100000 раз, замеряется общее время выполнения каждого прохода и усредняется.

Не все LambdaJ тесты запустились в JRE 8, в таблице они помечены звездочкой, результаты для них измерялись в JRE 7. В одном тесте лямбда-выражение уронило виртуальную машину совсем.

Тесты исполнялись на компьютере со следующей конфигурацией: Core i5 760 2.8 GHz 8 GB Win 7 sp-1 64 bit lambda-8-b45-windows-x64-24_jun_2012 jdk-7u4-windows-x64.

В итоге итеративный код дал самые лучшие результаты. Уверенное второе место у Guava, почти во всех тестах она дала аналогичные результаты с итеративным подходом. JDK лямбды тоже дают вполне соизмеримые с итеративным подходом результаты, то есть предоставляют вполне удовлетворительную замену итерациям и анонимным классам. И JDK 8, и Guava, и итеративный подход дают очень похожие результаты, но LamdaJ отстает чуть ли не на порядок, причина скорее всего кроется в широком использовании reflection.

Следует отметить, что у Guava и у JDK лямбд есть одна особенность при работе с коллекциями. Они часто возвращают не скопированную коллекцию, а её live view, которое откладывает вычисления до момента обращения к элементам этого view. Поэтому в тестах все live view были явно преобразованы в коллекции. Иначе некоторые тесты давали бы результат практически в 0 миллисекунд. Еще нужно сказать, что в реальной жизни никто не требует делать выбор в пользу какого либо подхода, например Guava будет отлично сочетаться с лямбда-выражениями.

Пример

Для примера как выглядит исходный код для каждого из подходов можно вглянуть на тест FindAgeOfYoungestWhoBoughtForMoreThan50000Test. В этом тесте дан список продаж автосалона, и в этом списке находится возраст самого молодого покупателя сделавшего покупку больше чем за 50000 каких-нибудь денег.

Итеративный подход

int age = Integer.MAX_VALUE;
for (final Sale sale : db.getSales()) {
    if (sale.getCost() > 50000.00) {
        final int buyerAge = sale.getBuyer().getAge();
        if (buyerAge < age) {
            age = buyerAge;
        }
    }
}

LambdaJ

final int age = Lambda.min(forEach(select(db.getSales(), having(on(Sale.class).getCost(),
                    greaterThan(50000.00)))).getBuyer(), on(Person.class).getAge());

JDK 8 lambda

final int age = Collections.min(db.getSales()
                    .filter((Sale sale)->sale.getCost() > 50000.00)
                    .<Integer>map((Sale sale)->sale.getBuyer().getAge())
                    .into(new ArrayList<Integer>()));

Guava

final int age = Collections.min(transform(filter(db.getSales(), new Predicate<Sale>() {
                @Override
                public boolean apply(final Sale input) {
                    return input.getCost() > 50000.00;
                }
            }), new Function<Sale, Integer>() {
                @Override
                public Integer apply(final Sale input) {
                    return input.getBuyer().getAge();
                }
            }));

Ссылки

Автор: dmmm

* - обязательные к заполнению поля