Oto dwa rozwiązania do wykonania 4.9 w Scali Cay Horstmanna dla Niecierpliwych: "Napisz funkcję lteqgt (wartości: Array [Int], v: Int), która zwraca potrójny zawierający wartości mniejsze niż v, równe v i większe niż v. " Jeden używa rekurencji ogona, drugi używa pętli while. Myślałem, że oba będą kompilować się do podobnego kodu bajtowego, ale pętla while jest wolniejsza niż rekursja ogona o czynnik prawie 2. Sugeruje to, że moja metoda while jest źle napisana.Dlaczego rekurencja rekina Scala jest szybsza niż pętla 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)
}
}
Dostosuj rozmiar bigArray zgodnie z rozmiarem sterty. Oto przykładowe dane wyjściowe:
Time = 245.110899 millis
(50004367,2003090,47992543)
Time = 465.836894 millis
(50004367,2003090,47992543)
Dlaczego ta metoda jest o wiele wolniejsza od metody tailrec? Naiwnie wersja tailrec wygląda na nieco wadę, ponieważ zawsze musi wykonywać 3 "jeśli" sprawdza dla każdej iteracji, podczas gdy wersja while często wykonuje tylko 1 lub 2 testy z powodu innej konstrukcji. (Uwaga: odwrócenie kolejności, w której wykonuję dwie metody, nie ma wpływu na wynik). Wynika
Często się nad tym zastanawiałem. Odpowiedź na pewno leży w JIT. Byłoby interesujące powtórzyć test porównawczy, całkowicie wyłączając JIT. –
Zobacz wyniki w https://stackoverflow.com/a/48143130/1172685 gdzie pętla while jest znacznie szybsza niż rekursja ogonowa (scala 2.12.x z testami skalowalności, które próbują zarządzać niespójnościami JVM). –