Scala For 内包表記は実際には非常に遅いと言われています。私が与えられた理由は、Java の制限により、渡された関数を呼び出すために、内包表記 (以下で使用される「reduce」など) で反復ごとに一時オブジェクトを生成する必要があるためです。
これは本当ですか?以下のテストはこれを裏付けているようですが、なぜそうなのかは完全にはわかりません.
これは、「ラムダ」または匿名関数には意味がありますが、非匿名関数には意味がありません。
私のテストでは、list.reduce に対して for ループを実行しました (以下のコードを参照)。各反復が reduce に渡された関数とまったく同じ関数を呼び出した場合でも、2 倍以上高速であることがわかりました。
これは驚くほど直観に反するものだと思います (Scala ライブラリは可能な限り最適になるように慎重に作成されていると考える人もいるでしょう)。
私がまとめたテストでは、5 つの異なる方法で同じループを実行しました (オーバーフローに関係なく、1 から 100 万までの数値を合計します)。
- 値の配列に対する for ループ
- for ループ、ただしインライン演算の代わりに関数を呼び出す
- for ループ、加算関数を含むオブジェクトの作成
- list.reduce、i に無名関数を渡す
- list.reduce、オブジェクト メンバー関数を渡す
結果は次のとおりです: テスト: 最小/最大/平均 (ミリ秒)
1. 27/157/64.78
2. 27/192/65.77 <--- note the similarity between tests 1,2 and 4,5
3. 139/313/202.58
4. 63/342/150.18
5. 63/341/149.99
ご覧のとおり、「理解のため」のバージョンは「インスタンスごとに新しいものを使用する」の順序であり、匿名関数と非匿名関数の両方のバージョンで実際に「新しい」が実行される可能性があることを意味します。
方法論: 以下のコード (テスト コールは削除されています) は、すべてのバージョンが同じライブラリ コードを実行するように、単一の .jar ファイルにコンパイルされました。各反復の各テストは、ヒープ サイズの問題を取り除くために、新しい JVM で呼び出されました (つまり、scala -cp ... 各テストに対して)。
class t(val i: Int) {
def summit(j: Int) = j + i
}
object bar {
val biglist:List[Int] = (1 to 1000000).toList
def summit(i: Int, j:Int) = i+j
// Simple for loop
def forloop: Int = {
var result: Int = 0
for(i <- biglist) {
result += i
}
result
}
// For loop with a function instead of inline math
def forloop2: Int = {
var result: Int = 0
for(i <- biglist) {
result = summit(result,i)
}
result
}
// for loop with a generated object PER iteration
def forloop3: Int = {
var result: Int = 0
for(i <- biglist) {
val t = new t(result)
result = t.summit(i)
}
result
}
// list.reduce with an anonymous function passed in
def anonymousfunc: Int = {
biglist.reduce((i,j) => {i+j})
}
// list.reduce with a named function
def realfunc: Int = {
biglist.reduce(summit)
}
// test calling code excised for brevity. One example given:
args(0) match {
case "1" => {
val start = System.currentTimeMillis()
forloop
val end = System.currentTimeMillis()
println("for="+(end - start))
}
...
}