Cay Horstmann's ImpalaのScalaで4.9を試してみるには、次の2つの解決策があります:v v未満の値のカウントを含むトリプルを返す関数lteqgt(values:Array [Int]、v:Int) 、およびvより大きい。 1つはテール再帰を使用し、もう1つはwhileループを使用します。私は両方とも同様のバイトコードにコンパイルすると思ったが、whileループはtailの再帰よりも2倍ほど遅い。これは私のwhileメソッドがひどく書かれていることを示唆している。なぜ私のScalaのテール再帰はwhileループより速いのですか?
import scala.annotation.tailrec
import scala.util.Random
object PerformanceTest {
def main(args: Array[String]): Unit = {
val bigArray:Array[Int] = fillArray(new Array[Int](100000000))
println(time(lteqgt(bigArray, 25)))
println(time(lteqgt2(bigArray, 25)))
}
def time[T](block : => T):T = {
val start = System.nanoTime : Double
val result = block
val end = System.nanoTime : Double
println("Time = " + (end - start)/1000000.0 + " millis")
result
}
@tailrec def fillArray(a:Array[Int], pos:Int=0):Array[Int] = {
if (pos == a.length)
a
else {
a(pos) = Random.nextInt(50)
fillArray(a, pos+1)
}
}
@tailrec def lteqgt(values: Array[Int], v:Int, lt:Int=0, eq:Int=0, gt:Int=0, pos:Int=0):(Int, Int, Int) = {
if (pos == values.length)
(lt, eq, gt)
else
lteqgt(values, v, lt + (if (values(pos) < v) 1 else 0), eq + (if (values(pos) == v) 1 else 0), gt + (if (values(pos) > v) 1 else 0), pos+1)
}
def lteqgt2(values:Array[Int], v:Int):(Int, Int, Int) = {
var lt = 0
var eq = 0
var gt = 0
var pos = 0
val limit = values.length
while (pos < limit) {
if (values(pos) > v)
gt += 1
else if (values(pos) < v)
lt += 1
else
eq += 1
pos += 1
}
(lt, eq, gt)
}
}
ヒープサイズに従って、bigArrayのサイズを調整します。ここにいくつかのサンプル出力があります:
Time = 245.110899 millis
(50004367,2003090,47992543)
Time = 465.836894 millis
(50004367,2003090,47992543)
なぜテールレックより遅いのですか?純粋にテールレックバージョンはわずかな欠点があるように見えます。なぜなら、すべての繰り返しに対して3つの "if"チェックを常に実行しなければならないのに対して、whileバージョンはelse構造のために1つまたは2つのテストしか実行しないことが多いからです。 (NBは2つの方法を実行する順序を逆にしても結果には影響しません)。
私はよくそれについて自分自身を疑問に思っています。確かにその答えはJITにある。 JITを完全に無効にしながらベンチマークを繰り返すことは興味深いでしょう。 –
結果を参照してください。https://stackoverflow.com/a/48143130/1172685ここで、whileループは末尾再帰よりもはるかに高速です(JVMの不一致を管理しようとするscalameterベンチマークを持つscala 2.12.x)。 –