Scalaでさまざまなコレクションを試して要素を合計しましたが、Javaが配列を合計するよりもはるかに低速です(for
サイクルあり)。ScalaをJava配列と同じくらい高速にする方法はありますか?
Scala 2.8の配列はJavaの場合と同じになると聞きましたが、実際にははるかに低速です。
Scalaでさまざまなコレクションを試して要素を合計しましたが、Javaが配列を合計するよりもはるかに低速です(for
サイクルあり)。ScalaをJava配列と同じくらい高速にする方法はありますか?
Scala 2.8の配列はJavaの場合と同じになると聞きましたが、実際にははるかに低速です。
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
を機能させるためにコレクション(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
これで、単純に合計を使用できます。
val values = Array.fill[Double](numValues)(0)
val sumOfValues = values.sum
適切な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は関数型言語なので、これは適切な方法です。
表示していないコードのパフォーマンスが、表示していないベンチマークで表示していない他のコードよりも悪い理由を説明するのは非常に困難です。
一つには、あなたはこの質問とその受け入れられた答えに興味があるかもしれません。ただし、JITは予測が難しい方法でコードを最適化するため、JVMコードのベンチマークは困難です(これが、JITがコンパイル時に従来の最適化を上回る理由です)。
Scala2.8Array
は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の対応するコードと同じくらい高速に実行されます。
タイミングだけが問題ではありません。sum
オーバーフローの問題が発生する可能性があります。
scala> Array(2147483647,2147483647).sum
res0: Int = -2
foldLeft
この場合、aを使用したシードLong
が望ましいです。
scala> Array(2147483647,2147483647).foldLeft(0L)(_+_)
res1: Long = 4294967294
編集:
Long
最初から使用できます:
scala> Array(2147483647L,2147483647L).sum
res1: Long = 4294967294