В середине 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();
}
}));
Ссылки
- Исходный код тестов github.com/dmalch/lambda-comparison
- Анализ производительности LamdaJ code.google.com/p/lambdaj/wiki/PerformanceAnalysis
- Загрузить JDK 8 без лямбд-выражений jdk8.java.net/download.html
- Загрузить JDK 8 с лямбда-выражениями jdk8.java.net/lambda/
- Статья на хабре про LamdaJ code.google.com/p/lambdaj/
Автор: dmmm