Я заинтересован в Scala, и у меня есть один основной вопрос, на который я не могу найти ответ: в общем, есть ли разница в производительности и использовании памяти между Scala и Java?
Я заинтересован в Scala, и у меня есть один основной вопрос, на который я не могу найти ответ: в общем, есть ли разница в производительности и использовании памяти между Scala и Java?
Ответы:
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 я могу выполнять утомительные, не критичные к производительности части, выполняемые с меньшими усилиями, и тратить больше моего внимания на оптимизацию алгоритмов и код для критичных к производительности частей.
Я новый пользователь, поэтому я не могу добавить комментарий к ответу Рекса Керра выше (разрешить новым пользователям «отвечать», но не «комментировать», кстати, очень странное правило).
Я подписался просто для того, чтобы ответить на «фу, 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 секунд на обдумывание глубоких алгоритмических мыслей), но я также могу вводить свой код в состязаниях по запутыванию и потенциально зарабатывать дополнительные деньги для праздники.
Arrays.stream(array).map(mapping::get).filter(x->x!=null).toArray(File[]::new);
Напишите ваш Scala как Java, и вы можете ожидать, что будет генерироваться почти идентичный байт-код - с почти идентичными метриками.
Напишите это более «идиоматически», с неизменяемыми объектами и функциями более высокого порядка, и это будет немного медленнее и немного больше. Единственное исключение из этого практического правила - при использовании объектов общего назначения, в которых параметры типа используют @specialised
аннотацию, это создаст еще больший байт-код, который может опередить производительность Java, избегая упаковки / распаковки.
Также стоит упомянуть тот факт, что больше памяти / меньше скорости - неизбежный компромисс при написании кода, который может выполняться параллельно. Идиоматический код Scala гораздо более декларативен по своей природе, чем типичный код Java, и зачастую он не более чем в 4 символа ( .par
) от полной параллельности.
Так что если
Тогда вы бы сказали, что код Scala теперь на 25% медленнее или в 3 раза быстрее?
Правильный ответ зависит от того, как именно вы определяете «производительность» :)
.par
в 2.9.
.par
.
map
метод, будет исчезающе малым.
Тесты компьютерного языка:
Тест скорости java / scala 1.71 / 2.25
Тест памяти java / scala 66.55 / 80.81
Итак, эти тесты говорят, что java быстрее на 24%, а scala использует на 21% больше памяти.
В целом, это не имеет большого значения и не должно иметь значения в реальных приложениях, где большую часть времени занимает база данных и сеть.
Итог: если Scala делает вас и вашу команду (и людей, которые принимают проект, когда вы уходите) более продуктивными, то вы должны пойти на это.
Другие ответили на этот вопрос относительно узких циклов, хотя, кажется, есть очевидная разница в производительности между примерами Рекса Керра, которые я прокомментировал.
Этот ответ действительно нацелен на людей, которые могут исследовать необходимость оптимизации в узком контуре как недостаток дизайна.
Я относительно новичок в Scala (около года или около того), но ощущение того, что он до сих пор заключается в том, что он позволяет вам относительно легко отложить многие аспекты проектирования, реализации и исполнения (при достаточном базовом чтении и экспериментах :)
Отложенные конструктивные особенности:
Отложенные особенности реализации:
Особенности отложенного выполнения: (извините, ссылок нет)
Эти функции, для меня, являются теми, которые помогают нам прокладывать путь к быстрым, жестким приложениям.
Примеры Рекса Керра отличаются тем, какие аспекты исполнения откладываются. В примере 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 уже далеко продвинулись по этому пути.
Презентация @higherkinded на эту тему - соображения производительности Scala, которые делают некоторые сравнения Java / Scala.
Инструменты:
Отличный блог пост:
Java и Scala компилируются в байт-код JVM, поэтому разница не так уж велика. Лучшее сравнение, которое вы можете получить, - это, вероятно, игра с тестами компьютерных языков , которая, по сути, говорит, что Java и Scala имеют одинаковое использование памяти. В некоторых из перечисленных тестов Scala работает лишь немного медленнее, чем Java, но это может быть просто потому, что реализация программ отличается.
Правда, они оба настолько близки, что не о чем беспокоиться. Увеличение производительности, которое вы получаете, используя более выразительный язык, такой как Scala, стоит гораздо больше, чем минимальный (если таковой имеется) удар по производительности.
Java and Scala both compile down to JVM bytecode,
которое было объединено с so
заявлением, о котором diffence isn't that big.
я хотел показать, что so
это всего лишь риторический трюк, а не спорный вывод.
Пример 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.