Прежде всего, я хотел бы прояснить, что это не вопрос «язык против языка», чтобы определить, что лучше.
Я давно использую Java и намерен продолжать его использовать. Параллельно с этим я в настоящее время изучаю Scala с большим интересом: кроме незначительных вещей, которые требуют некоторого привыкания, у меня сложилось впечатление, что я действительно могу очень хорошо работать на этом языке.
Мой вопрос: как программное обеспечение, написанное на Scala, сравнивается с программным обеспечением, написанным на Java, с точки зрения скорости выполнения и потребления памяти? Конечно, на этот вопрос в целом сложно ответить, но я ожидаю, что конструкции более высокого уровня, такие как сопоставление с образцом, функции высшего порядка и т. Д., Вносят некоторые накладные расходы.
Тем не менее, мой текущий опыт работы с Scala ограничен небольшими примерами под 50 строками кода, и я до сих пор не выполнил никаких тестов. Итак, у меня нет реальных данных.
Если оказалось, что в Scala есть некоторые накладные расходы по сравнению с Java, имеет ли смысл смешивать проекты Scala / Java, где можно кодировать более сложные части в Scala и критические по производительности части в Java? Это обычная практика?
РЕДАКТИРОВАТЬ 1
Я выполнил небольшой тест: построил список целых чисел, умножил каждое целое число на два и поместил его в новый список, распечатал полученный список. Я написал реализацию Java (Java 6) и реализацию Scala (Scala 2.9). Я запустил оба на Eclipse Indigo под Ubuntu 10.04.
Результаты сопоставимы: 480 мс для Java и 493 мс для Scala (в среднем за 100 итераций). Вот фрагменты, которые я использовал.
// Java
public static void main(String[] args)
{
long total = 0;
final int maxCount = 100;
for (int count = 0; count < maxCount; count++)
{
final long t1 = System.currentTimeMillis();
final int max = 20000;
final List<Integer> list = new ArrayList<Integer>();
for (int index = 1; index <= max; index++)
{
list.add(index);
}
final List<Integer> doub = new ArrayList<Integer>();
for (Integer value : list)
{
doub.add(value * 2);
}
for (Integer value : doub)
{
System.out.println(value);
}
final long t2 = System.currentTimeMillis();
System.out.println("Elapsed milliseconds: " + (t2 - t1));
total += t2 - t1;
}
System.out.println("Average milliseconds: " + (total / maxCount));
}
// Scala
def main(args: Array[String])
{
var total: Long = 0
val maxCount = 100
for (i <- 1 to maxCount)
{
val t1 = System.currentTimeMillis()
val list = (1 to 20000) toList
val doub = list map { n: Int => 2 * n }
doub foreach ( println )
val t2 = System.currentTimeMillis()
println("Elapsed milliseconds: " + (t2 - t1))
total = total + (t2 - t1)
}
println("Average milliseconds: " + (total / maxCount))
}
Таким образом, в этом случае кажется, что издержки Scala (с использованием range, map, lambda) действительно минимальны, что недалеко от информации, предоставленной World Engineer.
Может быть, есть другие конструкции Scala, которые следует использовать с осторожностью, потому что они особенно тяжелы для выполнения?
РЕДАКТИРОВАТЬ 2
Некоторые из вас отметили, что println во внутренних циклах занимают большую часть времени выполнения. Я удалил их и установил размер списков 100000 вместо 20000. В результате среднее значение составило 88 мс для Java и 49 мс для Scala.
источник
Ответы:
Есть одна вещь, которую вы можете сделать кратко и эффективно в Java, чего нельзя делать в Scala: перечисления. Для всего остального, даже для медленных конструкций в библиотеке Scala, вы можете получить эффективные версии, работающие в Scala.
Таким образом, по большей части вам не нужно добавлять Java в ваш код. Даже для кода, использующего перечисления в Java, в Scala часто есть решение, которое является адекватным или хорошим - я помещаю исключение в перечисления, которые имеют дополнительные методы и чьи значения констант int используются.
Что касается того, что нужно остерегаться, вот некоторые вещи.
Если вы используете шаблон обогащения моей библиотеки, всегда конвертируйте в класс. Например:
Остерегайтесь методов сбора данных - поскольку они большей частью полиморфны, JVM не оптимизирует их. Вам не нужно избегать их, но обратите внимание на это на критических разделах. Имейте в виду, что
for
в Scala реализованы вызовы методов и анонимные классы.Если вы используете класс Java, например
String
,Array
илиAnyVal
классы, соответствующие примитивам Java, предпочтите методы, предоставляемые Java, когда существуют альтернативы. Например, используйтеlength
наString
иArray
вместоsize
.Избегайте небрежного использования неявных преобразований, поскольку вы можете использовать преобразования по ошибке, а не по замыслу.
Расширьте классы вместо черт. Например, если вы расширяете
Function1
, расширьтеAbstractFunction1
вместо этого.Использование
-optimise
и специализация, чтобы получить большую часть Scala.Поймите, что происходит:
javap
ваш друг, и так же куча флагов Scala, которые показывают, что происходит.Идиомы Scala предназначены для улучшения правильности и делают код более лаконичным и понятным. Они не предназначены для скорости, поэтому, если вам нужно использовать
null
вместоOption
критического пути, сделайте это! Есть причина, по которой Scala является мультипарадигмой.Помните, что истинной мерой производительности является выполнение кода. Посмотрите на этот вопрос пример того, что может произойти, если вы игнорируете это правило.
источник
Согласно игре Benchmarks Game для одноядерной 32-битной системы, Scala в среднем на 80% быстрее, чем Java. Производительность примерно одинакова для компьютера Quad Core x64. Даже использование памяти и плотность кода в большинстве случаев очень похожи. Я бы сказал, основываясь на этом (довольно ненаучном) анализе, что вы правы, утверждая, что Scala добавляет некоторые издержки Java. Похоже, что это не добавляет тонны накладных расходов, поэтому я подозреваю, что диагноз предметов более высокого порядка, занимающих больше места / времени, является наиболее правильным.
источник
Int
,Char
и т.д. , когда это возможно. В то время как циклы так же эффективны в Scala.Function
классов. Если вы передаете лямбдаmap
-выражение, необходимо создать экземпляр анонимного класса (и, возможно, придется передать некоторые локальные объекты), а затем каждая итерация будет иметь дополнительные издержки вызова функции (с передачей некоторых параметров) отapply
вызовов.scala.util.Random
являются просто обертками вокруг эквивалентных классов JRE. Дополнительный вызов функции является немного расточительным.java.lang.Math.signum(x)
гораздо более прямой, чемx.signum()
, который преобразует вRichInt
и обратно.источник
сокращает время с 800 мс до 20 в моей системе.
источник