這裏有兩個解決方案在Cay Horstmann的Scala中練習4.9爲Impatient:「編寫一個函數lteqgt(values:Array [Int],v:Int),返回一個包含小於v值的計數的三元組,等於v ,並且大於v「。一個使用尾遞歸,另一個使用while循環。我認爲兩者都會編譯成相似的字節碼,但while循環比尾遞歸要慢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)
爲什麼while方法比tailrec慢得多?天真的tailrec版本看起來有點不利,因爲它必須始終執行3次「if」檢查每次迭代,而while版本通常只會執行1次或2次測試,這是由於else結構。 (NB逆轉我執行這兩種方法的順序不會影響結果)。
我經常想知道我自己。答案肯定在於JIT。在禁用JIT的同時重複基準會很有意思。 – 2012-02-07 00:07:22
在https://stackoverflow.com/a/48143130/1172685查看結果,其中while循環比尾遞歸快得多(scala 2.12.x與試圖管理JVM不一致性的scalameter基準測試)。 – 2018-01-08 01:29:29