Aquí hay dos soluciones para el ejercicio 4.9 en Scala para el impaciente de Cay Horstmann: "Escribe una función lteqgt (valores: Array [Int], v: Int) que devuelve un triple conteniendo los recuentos de valores menores que v, igual a v y más grande que v. " Uno usa recursividad de cola, el otro usa un ciclo while. Pensé que ambos compilarían un bytecode similar pero el ciclo while es más lento que la recursión de cola en un factor de casi 2. Esto me sugiere que mi método while está mal escrito.¿Por qué mi Scala tail-recursión es más rápida que el ciclo 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)
}
}
Ajuste el tamaño de bigArray de acuerdo con su tamaño de almacenamiento dinámico. Aquí está un ejemplo de salida:
Time = 245.110899 millis
(50004367,2003090,47992543)
Time = 465.836894 millis
(50004367,2003090,47992543)
¿Por qué es el método mientras tanto más lento que el tailrec? Ingenuamente, la versión tailrec parece estar en una ligera desventaja, ya que siempre debe realizar 3 "si" para cada iteración, mientras que la versión while a menudo solo realizará 1 o 2 pruebas debido a la construcción else. (Nota: al invertir el orden en que realizo los dos métodos, no afecta el resultado). resulta
A menudo me he preguntado sobre eso yo mismo. La respuesta seguramente está en JIT. Sería interesante repetir el punto de referencia mientras deshabilita el JIT por completo. –
Consulte los resultados en https://stackoverflow.com/a/48143130/1172685 donde un ciclo while es mucho más rápido que la recursión de cola (scala 2.12.x con parámetros de referencia de scalameter que intentan administrar inconsistencias de JVM). –