web-dev-qa-db-ja.com

Scalaでコレクションを合計する最も速い方法は何ですか

私はScalaの要素を合計するためにさまざまなコレクションを試しましたが、それらはJavaの合計よりも遅いです(forサイクルを使用) 。ScalaをJava配列と同じくらい高速にするための方法はありますか?

scala= 2.8の配列はJavaの配列と同じになると聞きましたが、実際にははるかに遅いです

22
Tala

Whileループでの配列へのインデックス付けは、ScalaでJavaと同じくらい高速です。(Scalaの "for"ループは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と同じくらい高速です。たとえば、doubleの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 in 2.8)それを機能させるには、そして多くの場合、プリミティブもボックス化する必要があります。

とにかく、ベンチマークテストでは、これらの2つの関数はお友達です。

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
Rex Kerr

これで単純に合計を使用できます。

val values = Array.fill[Double](numValues)(0)

val sumOfValues = values.sum
11
BAR

表示していない一部のコードが、表示していないベンチマークで表示していない他のコードよりもパフォーマンスが悪い理由を説明するのは非常に困難です。

この質問 とその受け入れられた答えに興味があるかもしれません。ただし、JITは予測が困難な方法でコードを最適化するため、JVMコードのベンチマークは困難です(そのため、JITはコンパイル時に従来の最適化に勝っています)。

6

Scala 2.8 Arrayare 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
Randall Schulz

適切なscalaまたは機能的でこれを行うことでした:

val numbers = Array(1, 2, 3, 4, 5)
val sum = numbers.reduceLeft[Int](_+_)

構文の完全な説明については、このリンクを確認してください: http://www.codecommit.com/blog/scala/quick-explanation-of-scalas-syntax

これが他の回答で説明されている方法で実行するよりも速いとは思いませんが、私はテストしていないのでわかりません。私の意見では、Scalaは関数型言語であるため、これは適切な方法です。

4
ayushn21

タイミングだけが問題ではありません。 sumを使用すると、オーバーフローの問題が発生する可能性があります。

scala> Array(2147483647,2147483647).sum
res0: Int = -2

この場合、foldLeftLongをシードすることをお勧めします

scala> Array(2147483647,2147483647).foldLeft(0L)(_+_)
res1: Long = 4294967294

編集:Longは最初から使用できます:

scala> Array(2147483647L,2147483647L).sum
res1: Long = 4294967294
3
rvazquezglez