В середине 2013 с выходом Java 8, язык начал поддерживать lambda-выражения, с тех пор минуло 4 года, было выпущено множество update-ов, грядет уже и выход Java 9 (которую, вероятно, мы сможем увидеть в этом году, не смотря на постоянные переносы сроков), таким образом на стыке времён, хотелось бы подвести итоги и оценить производительность нового в java и старого, как мир инструмента, дав ему количественную оценку.
Оценки производительности уже неоднократно производились, как в прочем обсуждались достоинства и недостатки lambda-выражений:
к
первым можно отнести:
— упрощение и сокращение кода
— возможность реализации функционального стиля
— возможность параллельных вычислений
ко
вторым:
— уменьшение производительности
— трудность debug'а
Неплохая методика и тесты были описаны и использованы еще 5 лет назад пользователем
dmmm (
https://habrahabr.ru/post/146718/), собственно они за основу и были взяты.
Если, в вкратце то сами тесты поставлялись с проектом
LambdaJ.
dmmm добавил тесты для проекта Guava и откорректировал их после обсуждения с habr-сообществом (2012).
Мною (то бишь
blutovi) были переписаны все lambda-тесты (поскольку тестирование проходило на альфа-версиях java, и в текущем состоянии код просто не компилируем), также при помощи Java Mission Control было проанализировано состояние памяти и для каждого теста приложен jfr-файл с результатами.
(Java Mission Control — средство диагностики и мониторинга JVM — доступно начиная с версии java 7 update 40, как результат объединения идей двух JVM:
HotSpot от Sun и
JRockit от Oracle).
Как тестировалось?
Сами junit-тесты состоят из трех фаз: инициализация коллекций, холостое выполнение, выполнение со сбором статистики (подсчет времени ведется только на последней стадии, а анализ работы с памятью осуществляется на протяжении всего цикла).
Замер времени осуществляется следующим образом: для каждой из
итераций, в течении времени
, происходит последовательный вызов тестируемого-алгоритма. По его истечении, определяется среднее арифметическое время выполнения алгоритма
:
путем деления суммарного времени его выполнения
на количество раз
, которое он успел выполнится. На основе
итераций, находим общее среднее время выполнения алгоритма:
На величины
(количество итераций) и
(время в течении, которого происходит вызов тестируемого алгоритма) можно повлиять изменив значения констант в классе ch.lambdaj.demo.AbstractMeasurementTest:
public static final int WARMUP_ITERATIONS_COUNT = 10;
public static final int ITERATIONS_COUNT = 10;
public static final int ITERATION_DURATION_MSEC = 1000;
Запуск тестов происходил с активированными параметрами для работы Java Mission Control, что позволило в автоматическом режиме формировать диагностические файлы.
Результаты
в абсолютных величинах (чем значения
меньше, тем
лучше)
|
for |
iterate |
guava |
jdk8 lambda |
|
time, ns |
heap, Mb |
time, ns |
heap, Mb |
time, ns |
heap, Mb |
time, ns |
heap, Mb |
Extract cars original cost |
220.4 |
313 |
200.6 |
324 |
188.1 |
302 |
266.2 |
333 |
Age of youngest who bought for > 50k |
6367 |
260 |
4245 |
271 |
6367 |
260 |
6157 |
278 |
Find buys of youngest person |
6036 |
270 |
6411.4 |
259 |
6206 |
260 |
6235 |
288 |
Find most bought car |
2356 |
167 |
2423 |
171 |
5882 |
193 |
2971 |
190 |
Find most costly sale |
49 |
71 |
58.1 |
66 |
431.6 |
297 |
196.1 |
82 |
Group sales by buyers and sellers |
12144 |
250 |
12053 |
254 |
8259 |
206 |
18447 |
242 |
Index cars by brand |
263.3 |
289 |
275.0 |
297 |
2828 |
226 |
307.5 |
278 |
Print all brands |
473.2 |
355 |
455.3 |
365 |
540.3 |
281 |
514.2 |
337 |
Select all sales of a Ferrari |
199.3 |
66 |
265.1 |
53 |
210.4 |
111 |
200.2 |
65 |
Sort sales by cost |
1075 |
74 |
1075 |
74 |
1069 |
72 |
1566 |
124 |
Sum costs where both are males |
67.0 |
63 |
72.9 |
58 |
215.9 |
88 |
413.7 |
114 |
в относительных величинах (100% — это база, чем значения
меньше, тем
лучше)
|
for |
iterate |
guava |
jdk8 lambda |
|
time |
heap |
time |
heap |
time |
heap |
time |
heap |
Extract cars original cost |
117% |
104% |
107% |
107% |
100% |
100% |
142% |
110% |
Age of youngest who bought for > 50k |
150% |
100% |
100% |
104% |
150% |
100% |
145% |
107% |
Find buys of youngest person |
100% |
104% |
106% |
100% |
103% |
100% |
103% |
111% |
Find most bought car |
100% |
100% |
103% |
102% |
250% |
116% |
126% |
114% |
Find most costly sale |
100% |
108% |
119% |
100% |
881% |
450% |
400% |
124% |
Group sales by buyers and sellers |
147% |
121% |
146% |
123% |
100% |
100% |
223% |
117% |
Index cars by brand |
100% |
128% |
104% |
131% |
1074% |
100% |
117% |
123% |
Print all brands |
104% |
126% |
100% |
130% |
119% |
100% |
113% |
120% |
Select all sales of a Ferrari |
100% |
125% |
133% |
100% |
106% |
209% |
100% |
123% |
Sort sales by cost |
101% |
103% |
101% |
103% |
100% |
100% |
147% |
172% |
Sum costs where both are males |
100% |
109% |
109% |
100% |
322% |
152% |
617% |
197% |
Выводы
Если обобщить, то можно отметить, что одни и те же вещи при помощи lambda-выражений можно реализовать по-разному: однако даже самые оптимальные варианты по производительности будут уступать простым решениям, а не оптимальные способны значительно сказаться на производительности.
Таким образом,
iterative подход дает возможность сделать акцент на
скорости выполнения, а
Guava позволяет акцентировать внимание на количестве
используемой памяти, сведя ее к минимуму. Использование же
lambda-выражений, похоже дает только эстетическое наслаждение (к моему великому удивлению).
Любые предложения как изменить и улучшить тесты приветствуются.
Ссылки
https://habrahabr.ru/post/331030/