Что самый быстрый путь состоит в том, чтобы суммировать набор в Scala

Я попробовал различные наборы в Scala для подведения итогов, это - элементы, и они намного медленнее, чем суммы Java, это - массивы (с for цикл). Существует ли способ для Scala быть с такой скоростью, как массивы Java?

Я услышал, что в массивах scala 2.8 будет то же как в Java, но они намного медленнее на практике

22
задан Eugene Yokota 15 December 2010 в 20:41
поделиться

3 ответа

Индексирование в массивы в цикле 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
29
ответ дан 29 November 2019 в 03:57
поделиться

Очень сложно объяснить, почему какой-то код, который вы не показывали, работает хуже, чем другой код, который вы не показывали в каком-то тесте, который вы не показывали.

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

6
ответ дан 29 November 2019 в 03:57
поделиться

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.

4
ответ дан 29 November 2019 в 03:57
поделиться
Другие вопросы по тегам:

Похожие вопросы: