Комментарии 31
"collections.chain.JavaFor.benchmark","avgt",1,5,1220522.342690,762881.441383,"ns/op",1000
"collections.chain.JavaStreams.benchmark","avgt",1,5,1159477.123056,779167.349278,"ns/op",1000
(762881.441383-779167.349278)/762881.441383
-0.021347888428739102
"collections.chain.JavaFor.benchmark","avgt",1,5,10819.745871,3922.925920,"ns/op",10
"collections.chain.JavaStreams.benchmark","avgt",1,5,10495.110603,5289.096391,"ns/op",10
(10495.110603-10819.745871)/10495.110603
-0.030932048291821133
Вы для Score Error разницу посчитали.
Но как уже ниже написали, есть «ощутимая» разница, которую плохо видно. Но в абсолютном выражении — это ничто.
3% это не так уж что бы и ничто…
Это в идеальном случае prefetch'а процессором.
Как только вылетите за кэш процессора или возьмете коллекцию, которая не умещается в памяти несколько раз, то… как вы думаете что происходит
В целом получилось, что для всех случаев у функционального подхода если и есть проигрыш, то он несущественный (в пределах погрешности измерений).
Манипуляции какие-то, у вас же логарифмический масштаб — и ваши "пределы погрешности" составляют разницу в три раза (скажем на последнем графике javaFor vs javaSteams)
это 0,5 миллисекунды…
И что, это много или мало?
Вот вроде айпад новый вышел с 120 фпс = 8 мс на кадр. Можно ли в кадре потерять 0.5 мс? Нет.
Если вы для вас скорость критична — это много.
Если вы разрабатываете «обычное» ПО, и у вас есть другие источники тормозов — база данных, пинг HTTP, накладные расходы от вашего любимого Application Server и т.д., то это мало.
База данных и пинг HTTP, они асинхронны и процессорное время внутри приложения не кушают. А вот код — вполне себе. Полмиллисекунды здесь, полторы там, всё это внутри часто вызываемой процедурки, а потом сервер полностью жрёт 8-ядерный Xeon на сотне пользователей.
Денег, которые принесут эти 100 пользователей, и денег, которые нужно будет отдать программистам и за аренду сервера. К сожалению, на шаге «перевод в деньги», очень многие обламываются, а он самый важный показатель. Потому, что серверные мощности со временем дешевеют, а программисты — дорожают.
А практика (увы, подкрепить цифрами не могу) написания кода показывает, что функциональный оптимизировать легче. Он гораздо более наглядно передает суть «что тут вообще происходит», чем императивная лапша.
Это означает лишь тот факт, что не следует перебирать коллекцию в 30 000 элементов каждый кадр.
Если у меня 30К объектов на игровом поле, как их прикажете обсчитывать?
Пытаясь вернуться в контекст статьи — если в коллекции «поле» 30000 объектов, то перед отрисовкой нужно сделать viewField.filter(isVisible()), вдруг в итоге останется вообще один объект, потому что игрок носом уперся в стену.
Отказаться от модели "каждый кадр отображаем все объекты" и придумать алгоритм по-лучше.
К примеру, в Factorio где-то посередине ветки .14 однажды отказались от поиска коллизий между конвейером и лежащим на земле предметом при каждом обновлении, и стали явно связывать предмет с конвейером при бросании предмета на землю или при строительстве конвейера.
А в версии .15 вместо хранения координат едущего по конвейеру предмета с обновлением на каждом кадре стали хранить расстояние до следующего предмета на конвейере (или до края конвейера) — такие расстояния надо обновлять только для первого предмета из группы.
Обе этих оптимизации ускоряли игру на несколько порядков ощутимее чем обсуждаемая тут разница между javaFor и javaStream.
Там, кстати, этих предметов в свободной игре во всяческих мега-заводах запросто может 30 тысяч набраться.
Вам не кажется странным сравнивать функциональщину и императивщину на задачах создания новой коллекции? Сравните на задачах вида "добавление элемента в список" или "сортировка огромных списков". И не забудьте померить потребление и фрагментацию памяти.
В среднем у меня получались в два раза медленнее ява стримы чем императивный код. В узких местах приходится ArrayList как ни крути.
Смешной факт: если не комбинировать потоки а просто возвращать новую коллекцию каждый раз, то скорость будет такая же. Нас накололи, никаких преимуществ по скорости ленивость не дает...
Нас накололи, никаких преимуществ по скорости ленивость не дает...
Для больших коллекций при большом количестве chain преобразований думаю ленивый подход лучше себя покажет
То-то я думаю как это в колтине цикл медленней. Вроде же одно и то же должно получаться. Так он из-за боксинга проигрывает!
В функциональном варианте компилятор не мудрит и компилирует это как обычный foreach на Integer. А вот в цикле компилятор обманул сам себя. Он видит что цикл по notnullable интам, потому превращает их в Integer.intValue(). После чего, радостно, боксит обратно, чтобы засунуть в result (это ведь ArrayList обычный).
Это еще раз очень наглядно демострирует бессмысленность бенчей без дальнейшего анализа. Ибо разница вообще не циклах. На любой коллекции небоксящихся типов (или боксящихся, но "с вопросиком") разницы вообще нет.
Если глянуть внутрь, например, foldLeft, можно увидеть var и цикл while.
Но тут понятно, что это локальная мутабельность, используемая для повышения производительности.
Зачем мне твои неизменяемые коллекции? Они же медленные