Я попробовал различные наборы в Scala для подведения итогов, это - элементы, и они намного медленнее, чем суммы Java, это - массивы (с for
цикл). Существует ли способ для Scala быть с такой скоростью, как массивы Java?
Я услышал, что в массивах scala 2.8 будет то же как в Java, но они намного медленнее на практике
Индексирование в массивы в цикле while выполняется в Scala так же быстро, как и в Java. (Цикл "for" в Scala не является низкоуровневой конструкцией, как в Java, поэтому он не будет работать так, как вы хотите.)
Таким образом, если в Java вы видите
for (int i=0 ; i < array.length ; i++) sum += array(i)
, в Scala вы должны написать
var i=0
while (i < array.length) {
sum += array(i)
i += 1
}
и если вы сделаете свои тесты правильно, вы не найдете разницы в скорости.
Если у вас все равно есть итераторы, то Scala в большинстве случаев так же быстр, как Java. Например, если у вас есть список двойников ArrayList и в Java вы добавляете их, используя
for (double d : arraylist) { sum += d }
, тогда в Scala вы будете примерно так же быстро - при использовании эквивалентной структуры данных, такой как ArrayBuffer - с
arraybuffer.foreach( sum += _ )
и не слишком далеко неправильно с любым из
sum = (0 /: arraybuffer)(_ + _)
sum = arraybuffer.sum // 2.8 only
Имейте в виду, что есть штраф за смешивание высокоуровневых и низкоуровневых конструкций. Например, если вы решите начать с массива, но затем используете для него «foreach» вместо индексации, Scala придется обернуть его в коллекцию ( ArrayOps
в 2.8), чтобы заставить его работать, и часто придется также упаковывать примитивы.
В любом случае, для тестирования производительности вам подойдут эти две функции:
def time[F](f: => F) = {
val t0 = System.nanoTime
val ans = f
printf("Elapsed: %.3f\n",1e-9*(System.nanoTime-t0))
ans
}
def lots[F](n: Int, f: => F): F = if (n <= 1) f else { f; lots(n-1,f) }
Например:
val a = Array.tabulate(1000000)(_.toDouble)
val ab = new collection.mutable.ArrayBuffer[Double] ++ a
def adSum(ad: Array[Double]) = {
var sum = 0.0
var i = 0
while (i<ad.length) { sum += ad(i); i += 1 }
sum
}
// Mixed array + high-level; convenient, not so fast
scala> lots(3, time( lots(100,(0.0 /: a)(_ + _)) ) )
Elapsed: 2.434
Elapsed: 2.085
Elapsed: 2.081
res4: Double = 4.999995E11
// High-level container and operations, somewhat better
scala> lots(3, time( lots(100,(0.0 /: ab)(_ + _)) ) )
Elapsed: 1.694
Elapsed: 1.679
Elapsed: 1.635
res5: Double = 4.999995E11
// High-level collection with simpler operation
scala> lots(3, time( lots(100,{var s=0.0;ab.foreach(s += _);s}) ) )
Elapsed: 1.171
Elapsed: 1.166
Elapsed: 1.162
res7: Double = 4.999995E11
// All low level operations with primitives, no boxing, fast!
scala> lots(3, time( lots(100,adSum(a)) ) )
Elapsed: 0.185
Elapsed: 0.183
Elapsed: 0.186
res6: Double = 4.999995E11
Очень сложно объяснить, почему какой-то код, который вы не показывали, работает хуже, чем другой код, который вы не показывали в каком-то тесте, который вы не показывали.
Во-первых, вас может заинтересовать этот вопрос и принятый на него ответ. Но тестирование кода JVM сложно, потому что JIT оптимизирует код способами, которые трудно предсказать (вот почему JIT превосходит традиционную оптимизацию во время компиляции).
Scala 2.8 Array
являются массивами JVM / Java и как таковые имеют идентичные характеристики производительности. Но это означает, что они не могут напрямую иметь дополнительные методы, объединяющие их с остальными коллекциями Scala. Чтобы создать иллюзию, что у массивов есть такие методы, существуют неявные преобразования в классы-обёртки, которые добавляют эти возможности. Если вы не будете осторожны, вы понесете непомерные накладные расходы, используя эти возможности.
В тех случаях, когда накладные расходы на итерацию критичны, вы можете явно получить итератор (или поддерживать целочисленный индекс, для индексированных последовательных структур типа Array
или других IndexedSeq
) и использовать цикл while
, который является конструкцией на уровне языка, не оперирующей функциями (литералами или иными), но может компилировать блоки поточного кода.
val l1 = List(...) // or any Iteralbe
val i1 = l1.iterator
while (i1.hasNext) {
val e = i1.next
// Do stuff with e
}
Такой код будет выполняться практически так же быстро, как и его аналог на Java.