我不是在问我应该使用递归还是迭代,或者它们之间比较快。我试图了解所花费的迭代和递归时间,而我想出了一个有趣的模式,两者都花费了时间,这就是文件顶部要比其他时间花费更多的时间。
例如:如果我在一开始为循环编写代码,则它花费的时间比递归要多,反之亦然。这两个过程所花费的时间之间的差异明显是大约30到40倍的巨大变化。
我的问题是:-
以下是我在同一文件中的代码,我使用的语言是scala?
display: block
在这种情况下,递归和while循环所花费的时间分别为986ms和20ms。
当我切换循环和递归的位置时,这意味着先循环然后递归,递归和while循环所花费的时间分别为1.69 sec和28 ms。
修改1: 如果递归代码位于顶部,则可以看到与bufferWriter相同的行为。但递归位于循环以下时,情况并非如此。当递归低于循环时,它花费几乎相同的时间,相差2到3毫秒。
答案 0 :(得分:2)
Scala不会编译为机器代码,而是会编译为“ Java虚拟机”(JVM)的字节码,然后JavaJVM将在本地处理器上解释该代码。 JVM使用多种机制来优化经常运行的代码,最终将经常调用的功能(“热点”)转换为纯机器代码。
这意味着测试功能的首次运行并不能很好地衡量最终性能。您需要通过多次运行测试代码来“热身” JIT编译器,然后再尝试测量花费的时间。
此外,如评论中所述,执行任何类型的I / O都会使计时变得非常不可靠,因为存在I / O阻塞的危险。如果可能的话,编写一个没有任何阻塞的测试用例。
答案 1 :(得分:2)
如果您想使自己相信tailrec优化是有效的,而无需依赖任何配置工具,则可以尝试以下方法:
遵循以下原则:
def compare(
xs: Array[(String, () => Unit)],
maxRepsPerBlock: Int = 10000,
totalRounds: Int = 100000,
warmupRounds: Int = 1000
): Unit = {
val n = xs.size
val times: Array[Long] = Array.ofDim[Long](n)
val rng = new util.Random
val indices = (0 until n).toList
var totalReps: Long = 0
for (round <- 1 to totalRounds) {
val order = rng.shuffle(indices)
val reps = rng.nextInt(maxRepsPerBlock / 2) + maxRepsPerBlock / 2
for (i <- order) {
var r = 0
while (r < reps) {
r += 1
val start = System.currentTimeMillis
(xs(i)._2)()
val end = System.currentTimeMillis
if (round > warmupRounds) {
times(i) += (end - start)
}
}
}
if (round > warmupRounds) {
totalReps += reps
}
}
for (i <- 0 until n) {
println(f"${xs(i)._1}%20s : ${times(i) / totalReps.toDouble}")
}
}
def gaussSumWhile(n: Int): Long = {
var acc: Long = 0
var i = 0
while (i <= n) {
acc += i
i += 1
}
acc
}
@annotation.tailrec
def gaussSumRec(n: Int, acc: Long = 0, i: Int = 0): Long = {
if (i <= n) gaussSumRec(n, acc + i, i + 1)
else acc
}
compare(Array(
("while", { () => gaussSumWhile(1000) }),
("@tailrec", { () => gaussSumRec(1000) })
))
打印内容如下:
while : 6.737733046257334E-5
@tailrec : 6.70325653896487E-5
即使上面的简单提示也足以创建一个基准,该基准显示while循环和尾部递归函数大致同时使用 。