Скала против Java, производительность и память? [закрыто]

161

Я заинтересован в Scala, и у меня есть один основной вопрос, на который я не могу найти ответ: в общем, есть ли разница в производительности и использовании памяти между Scala и Java?

Джон Смит
источник
3
Я слышал, что представление может быть очень близким. Я подозреваю, что это сильно зависит от того, что вы делаете. (как для Java против C)
Питер Лоури
Ответ на эти вопросы - «это зависит» - практически для любого сравнения системы X с системой Y. Плюс, это дубликат stackoverflow.com/questions/2479819/…
Джеймс Мур

Ответы:

262

Scala позволяет очень просто использовать огромное количество памяти, даже не осознавая этого. Это обычно очень сильно, но иногда может раздражать. Например, предположим, у вас есть массив строк (называемых array) и карта из этих строк в файлы (вызываемые mapping). Предположим, что вы хотите получить все файлы, которые находятся на карте и имеют строки длиной более двух. В Java вы можете

int n = 0;
for (String s: array) {
  if (s.length > 2 && mapping.containsKey(s)) n++;
}
String[] bigEnough = new String[n];
n = 0;
for (String s: array) {
  if (s.length <= 2) continue;
  bigEnough[n++] = map.get(s);
}

Уф! Тяжелая работа. В Scala самый компактный способ сделать то же самое:

val bigEnough = array.filter(_.length > 2).flatMap(mapping.get)

Легко! Но, если вы не достаточно знакомы с тем, как работают коллекции, вы можете не понимать, что этот способ создания дополнительного промежуточного массива (с filter) и дополнительного объекта для каждого элемента массиваmapping.get, который возвращает опция). Он также создает два функциональных объекта (один для фильтра и один для flatMap), хотя это редко является серьезной проблемой, поскольку функциональные объекты имеют небольшой размер.

В общем, использование памяти на примитивном уровне одинаково. Но библиотеки Scala имеют много мощных методов, которые позволяют очень легко создавать огромное количество (обычно недолговечных) объектов. Сборщик мусора, как правило, довольно хорошо справляется с подобным мусором, но если вы будете совершенно не замечать, какая память используется, вы, вероятно, столкнетесь с проблемами скорее в Scala, чем в Java.

Обратите внимание, что тест Scala кода Computer Languages ​​Game написан в довольно похожем на Java стиле, чтобы получить производительность, подобную Java, и, следовательно, использует память в стиле Java. Вы можете сделать это в Scala: если вы напишите свой код, похожий на высокопроизводительный код Java, это будет высокопроизводительный код Scala. (Вы можете написать его в более идиоматическом стиле Scala и при этом добиться хорошей производительности, но это зависит от специфики.)

Я должен добавить, что за время, потраченное на программирование, мой код Scala обычно быстрее, чем мой код Java, поскольку в Scala я могу выполнять утомительные, не критичные к производительности части, выполняемые с меньшими усилиями, и тратить больше моего внимания на оптимизацию алгоритмов и код для критичных к производительности частей.

Рекс Керр
источник
172
+1 за последний пункт. Это важный момент , что осталось из рассмотрения далеко слишком часто.
Кевин Райт
2
Я думал, что взгляды могут очень помочь с проблемами, которые вы упоминаете. Или не совсем с массивами конкретно?
Николя Пайетт
1
@ Кевин Райт - «Это жизненно важный момент, который слишком часто игнорируется» - это то, что легко сказать и трудно продемонстрировать, и расскажет нам кое-что о навыках Рекса Керра, а не о том, чего достигают другие, менее опытные.
igouy
1
>> в идиоматическом стиле с превосходной производительностью << В тестовой игре есть место для идиоматических программ Scala, которые не достигают «превосходной» производительности.
igouy
2
@RexKerr - ваш пример Java не ищет ключ сопоставления дважды для каждой возможной строки, где ваш пример Scala делает это только один раз после выбора строк? Т.е. они по-разному оптимизируются под разные наборы данных?
Сет
103

Я новый пользователь, поэтому я не могу добавить комментарий к ответу Рекса Керра выше (разрешить новым пользователям «отвечать», но не «комментировать», кстати, очень странное правило).

Я подписался просто для того, чтобы ответить на «фу, Java такая многословная и такая тяжелая работа», как инсинуация популярного ответа Рекса выше. Хотя вы, конечно, можете написать более лаконичный код Scala, приведенный пример Java явно раздут. Большинство разработчиков Java написали бы что-то вроде этого:

List<String> bigEnough = new ArrayList<String>();
for(String s : array) {
  if(s.length() > 2 && mapping.get(s) != null) {
    bigEnough.add(mapping.get(s));
  }
}

И, конечно, если мы собираемся притвориться, что Eclipse не выполняет большую часть фактической типизации за вас и что каждый сохраненный символ действительно делает вас лучшим программистом, то вы можете написать это:

List b=new ArrayList();
for(String s:array)
  if(s.length()>2 && mapping.get(s) != null) b.add(mapping.get(s));

Теперь я не только сэкономил время, необходимое мне для ввода полных имен переменных и фигурных скобок (что позволило мне потратить еще 5 секунд на обдумывание глубоких алгоритмических мыслей), но я также могу вводить свой код в состязаниях по запутыванию и потенциально зарабатывать дополнительные деньги для праздники.

Не спит
источник
7
Почему вы не являетесь членом клуба "хип язык месяца"? Хорошие комментарии. Мне особенно понравилось читать последний абзац.
Степанян
21
Великолепно поставлено! Я устаю от надуманных примеров, когда за завышенным Java-кодом следует какой-то тщательно сконструированный, краткий пример Scala (или какого-либо другого языка FP), а затем поспешно сделан вывод, что Scala из-за этого должен быть лучше Java. Кто нибудь написал что-нибудь значимое в Scala? ;-) И не говорите Твиттер ...
chrisjleu
2
Что ж, решение Rex предварительно выделяет память для массива, что ускоряет работу скомпилированного кода (поскольку с вашим подходом вы позволяете JVM периодически перераспределять ваш массив по мере его роста). Несмотря на то, что было задействовано больше печатания, в плане производительности это могло бы стать победителем.
Ашалынд
5
в то время как мы в этом, в java8 это будет:Arrays.stream(array).map(mapping::get).filter(x->x!=null).toArray(File[]::new);
bennyl
2
То, что делает Scala «лучше» в некотором смысле, чем Java, - это расширенные возможности системы типов, которые упрощают выражение более общих шаблонов в виде типов (таких как монады, функторы и т. Д.). Это позволяет вам создавать типы, которые не мешают вам из-за слишком строгих контрактов, как это часто бывает в Java. Строгие контракты, не основанные на фактических шаблонах в коде, являются причиной, по которой шаблоны инверсии ответственности необходимы только для правильного модульного тестирования вашего кода (сначала приходит на ум инъекция зависимости и адский XML). Дополнение краткость, которую приносит гибкость, это просто бонус.
Иосия
67

Напишите ваш Scala как Java, и вы можете ожидать, что будет генерироваться почти идентичный байт-код - с почти идентичными метриками.

Напишите это более «идиоматически», с неизменяемыми объектами и функциями более высокого порядка, и это будет немного медленнее и немного больше. Единственное исключение из этого практического правила - при использовании объектов общего назначения, в которых параметры типа используют @specialisedаннотацию, это создаст еще больший байт-код, который может опередить производительность Java, избегая упаковки / распаковки.

Также стоит упомянуть тот факт, что больше памяти / меньше скорости - неизбежный компромисс при написании кода, который может выполняться параллельно. Идиоматический код Scala гораздо более декларативен по своей природе, чем типичный код Java, и зачастую он не более чем в 4 символа ( .par) от полной параллельности.

Так что если

  • Scala-код занимает в 1,25 раза больше времени, чем Java-код в одном потоке
  • Он может быть легко разделен на 4 ядра (теперь распространено даже в ноутбуках)
  • для времени параллельного выполнения (1,24 / 4 =) 0,3125x исходной Java

Тогда вы бы сказали, что код Scala теперь на 25% медленнее или в 3 раза быстрее?

Правильный ответ зависит от того, как именно вы определяете «производительность» :)

Кевин Райт
источник
4
Кстати, вы можете упомянуть, что .parв 2.9.
Рекс Керр
26
>> Тогда вы бы сказали, что код Scala теперь сравнительно на 25% медленнее или в 3 раза быстрее? << Я бы сказал, почему вы не гипотетически сравниваете многопоточный код Java?
igouy
17
@igouy - Дело в том, что указанный гипотетический код не существует, императивный характер «более быстрого» Java-кода значительно усложняет параллелизацию, так что соотношение затрат и выгод означает, что это вряд ли произойдет вообще. С другой стороны, идиоматическая Scala, будучи гораздо более декларативной по своей природе, часто может быть сопряжена с не более чем тривиальным изменением.
Кевин Райт
7
Существование параллельных Java-программ не означает, что типичная Java-программа может быть легко адаптирована к параллелизму. Во всяком случае, я бы сказал, что конкретный стиль fork-join особенно редок в Java и должен быть явно закодирован, тогда как простые операции, такие как поиск минимального содержащегося значения или сумма значений в коллекции, могут тривиально выполняться параллельно в Scala, просто используя .par.
Кевин Райт
5
Нет, не могу. Подобные вещи являются фундаментальным строительным блоком для многих алгоритмов, и их присутствие на таком низком уровне в языке и стандартных библиотеках (те же стандартные библиотеки, которые будут использоваться всеми программами, а не только типичными), свидетельствует о том, что вы ' мы уже ближе к одновременности, просто выбирая язык. Например, отображение коллекции по своей природе подходит для распараллеливания, и число программ Scala, которые не используют этот mapметод, будет исчезающе малым.
Кевин Райт
31

Тесты компьютерного языка:

Тест скорости java / scala 1.71 / 2.25

Тест памяти java / scala 66.55 / 80.81

Итак, эти тесты говорят, что java быстрее на 24%, а scala использует на 21% больше памяти.

В целом, это не имеет большого значения и не должно иметь значения в реальных приложениях, где большую часть времени занимает база данных и сеть.

Итог: если Scala делает вас и вашу команду (и людей, которые принимают проект, когда вы уходите) более продуктивными, то вы должны пойти на это.

Питер Кнего
источник
34
Размер кода java / scala 3.39 / 2.21
хаммар
22
Будьте осторожны с такими числами, они звучат очень точно, в то время как в действительности они почти ничего не значат. Это не значит, что Scala всегда на 24% быстрее, чем Java в среднем и т. Д.
Jesper
3
Приведенные афайками цифры указывают на обратное: Java работает на 24% быстрее, чем scala. Но, как вы говорите - это микробенчмарки, которые не должны совпадать с тем, что происходит в реальных приложениях. И разный способ или решение проблемы на разных языках может привести к менее сопоставимым программам в конце концов.
пользователь неизвестен
9
«Если Scala сделает вас и вашу команду ...» Итог: вы узнаете об этом не раньше :-)
igouy
На странице справки по игре в бенчмарки приведен пример «Сравнение скорости и размера программы для двух языковых реализаций». Для Scala и Java соответствующее сравнение веб - страницы - shootout.alioth.debian.org/u64q/scala.php
igouy
20

Другие ответили на этот вопрос относительно узких циклов, хотя, кажется, есть очевидная разница в производительности между примерами Рекса Керра, которые я прокомментировал.

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

Я относительно новичок в Scala (около года или около того), но ощущение того, что он до сих пор заключается в том, что он позволяет вам относительно легко отложить многие аспекты проектирования, реализации и исполнения (при достаточном базовом чтении и экспериментах :)

Отложенные конструктивные особенности:

Отложенные особенности реализации:

Особенности отложенного выполнения: (извините, ссылок нет)

  • Потокобезопасные ленивые значения
  • Pass-по-имени
  • Монадические вещи

Эти функции, для меня, являются теми, которые помогают нам прокладывать путь к быстрым, жестким приложениям.


Примеры Рекса Керра отличаются тем, какие аспекты исполнения откладываются. В примере Java выделение памяти откладывается до тех пор, пока не будет рассчитан ее размер, тогда как пример Scala откладывает поиск отображения. Мне они кажутся совершенно разными алгоритмами.

Вот то, что я думаю, больше похоже на яблоки к яблокам для его примера на Java:

val bigEnough = array.collect({
    case k: String if k.length > 2 && mapping.contains(k) => mapping(k)
})

Никаких промежуточных коллекций, никаких Optionэкземпляров и т. Д. Это также сохраняет тип коллекции, так bigEnoughчто реализация типа is Array[File]- Array's collect, вероятно, будет делать что-то в соответствии с тем, что делает код Java Керра.

Функции отложенного проектирования, которые я перечислил выше, также позволили бы разработчикам Scala API-интерфейсов реализовывать эту быструю реализацию сбора для конкретных массивов в будущих выпусках, не нарушая API. Это то, что я имею в виду, ступая на путь к скорости.

Также:

val bigEnough = array.withFilter(_.length > 2).flatMap(mapping.get)

withFilterМетод , который я использовал здесь вместо filterисправления промежуточной проблемы сбора , но есть еще проблема экземпляра Option.


Одним из примеров простой скорости выполнения в Scala является регистрация.

В Java мы могли бы написать что-то вроде:

if (logger.isDebugEnabled())
    logger.debug("trace");

В Scala это просто:

logger.debug("trace")

потому что параметр сообщения для отладки в Scala имеет тип " => String", который я считаю функцией без параметров, которая выполняется при оценке, но которую документация называет pass-by-name.

EDIT {Функции в Scala являются объектами, поэтому здесь есть дополнительный объект. Для моей работы вес тривиального объекта стоит исключить возможность ненужной оценки сообщения журнала. }

Это не делает код быстрее, но повышает вероятность того, что он будет быстрее, и у нас меньше шансов на то, чтобы массово проходить и очищать код других людей.

Для меня это постоянная тема в Scala.


Жесткий код не может понять, почему Scala работает быстрее, хотя намекает немного.

Я чувствую, что это сочетание повторного использования кода и предела качества кода в Scala.

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

У меня большие надежды на то, что Scala сможет позволить нашим эйнштейнам реализовать гораздо более компетентные API, потенциально выраженные через DSL. Основные API в Scala уже далеко продвинулись по этому пути.

Сет
источник
Ваши записи в журнале - хороший пример ошибок производительности в Scala: logger.debug («trace») создает новый объект для функции без параметров.
jcsahnwaldt Восстановить Монику
Действительно - как это влияет на мою связанную точку?
Сет
Вышеупомянутые объекты также могут быть использованы для создания прозрачных управляющих структур IoC для эффективности. Да, тот же результат теоретически возможен в Java, но это будет что-то, что сильно повлияло / запутало способ написания кода - отсюда мой аргумент, что умение Scala откладывать многие элементы разработки программного обеспечения помогает нам двигаться к более быстрому коду - с большей вероятностью быстрее на практике по сравнению с немного более быстрой производительностью.
Сет
Хорошо, я перечитал это и написал «простая скорость выполнения» - я добавлю примечание. Хороший вопрос :)
Сет
3
Предсказуемый оператор if (в основном свободный от суперскалярного процессора) против размещения объектов + мусора. Java-код, очевидно, быстрее (обратите внимание, что он только оценивает условие, выполнение не достигнет оператора log.) В ответ на «Для моей работы вес тривиального объекта стоит исключить возможность ненужной оценки сообщения журнала». «.
Eloff
10

Java и Scala компилируются в байт-код JVM, поэтому разница не так уж велика. Лучшее сравнение, которое вы можете получить, - это, вероятно, игра с тестами компьютерных языков , которая, по сути, говорит, что Java и Scala имеют одинаковое использование памяти. В некоторых из перечисленных тестов Scala работает лишь немного медленнее, чем Java, но это может быть просто потому, что реализация программ отличается.

Правда, они оба настолько близки, что не о чем беспокоиться. Увеличение производительности, которое вы получаете, используя более выразительный язык, такой как Scala, стоит гораздо больше, чем минимальный (если таковой имеется) удар по производительности.

ryeguy
источник
7
Я вижу здесь логическую ошибку: оба языка компилируются в байт-код, но опытный программист и новичок - их код тоже компилируется в байт-код - но не в один и тот же байт-код, поэтому вывод, что разница не может быть такой большой может ошибаться. И действительно, в прежние времена цикл while мог быть намного, намного быстрее в scala, чем семантически эквивалентный цикл for (если я правильно помню, сегодня он намного лучше). И оба были скомпилированы в байт-код, конечно.
пользователь неизвестен
@user unknown - «цикл while может быть намного, намного быстрее в scala, чем семантически эквивалентный цикл for» - обратите внимание, что эти игровые программы Scala написаны с циклами while.
августа
@igouy: я говорил не о результатах этого микробенчмарка, а об аргументации. Истинное утверждение, Java and Scala both compile down to JVM bytecode, которое было объединено с soзаявлением, о котором diffence isn't that big.я хотел показать, что soэто всего лишь риторический трюк, а не спорный вывод.
пользователь неизвестен
3
удивительно неправильный ответ с удивительно высоким голосом.
Шабун
4

Пример Java на самом деле не является идиомой для типичных прикладных программ. Такой оптимизированный код можно найти в методе системной библиотеки. Но тогда он будет использовать массив правильного типа, то есть File [], и не будет генерировать исключение IndexOutOfBoundsException. (Различные условия фильтра для подсчета и сложения). Моя версия была бы (всегда (!) С фигурными скобками, потому что я не люблю тратить час на поиск ошибки, которая появилась, сэкономив 2 секунды, чтобы нажать одну клавишу в Eclipse):

List<File> bigEnough = new ArrayList<File>();
for(String s : array) {
  if(s.length() > 2) {
    File file = mapping.get(s);
    if (file != null) {
      bigEnough.add(file);
    }
  }
}

Но я мог бы принести вам много других уродливых примеров кода Java из моего текущего проекта. Я старался избегать общего стиля копирования и модификации, выделяя общие структуры и поведение.

В моем абстрактном базовом классе DAO у меня есть абстрактный внутренний класс для общего механизма кэширования. Для каждого конкретного типа объекта модели существует подкласс абстрактного базового класса DAO, в котором внутренний класс разделен на подклассы, чтобы обеспечить реализацию метода, который создает бизнес-объект при его загрузке из базы данных. (Мы не можем использовать инструмент ORM, потому что мы получаем доступ к другой системе через собственный API.)

Этот код подкласса и реализации не совсем понятен в Java и будет очень удобочитаемым в Scala.

MickH
источник