Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/scala/19.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Scala while循环和递归所花费的时间_Scala_Performance_Recursion_Tail Recursion - Fatal编程技术网

Scala while循环和递归所花费的时间

Scala while循环和递归所花费的时间,scala,performance,recursion,tail-recursion,Scala,Performance,Recursion,Tail Recursion,我不是在问我应该使用递归还是迭代,或者两者之间哪个更快。我试图理解迭代和递归所花费的时间,我提出了一个有趣的模式,在这两者所花费的时间中,文件顶部的任何一个都比另一个要花费更多的时间 例如:如果我在一开始为循环编写代码,那么它比递归要花更多的时间,反之亦然。在这两个过程中所花费的时间相差30到40倍 我的问题是: 循环和递归的顺序重要吗? 有与印刷有关的东西吗? 这种行为的可能原因是什么? 下面是我在同一个文件中的代码,我使用的语言是scala def count(x: Int): Unit

我不是在问我应该使用递归还是迭代,或者两者之间哪个更快。我试图理解迭代和递归所花费的时间,我提出了一个有趣的模式,在这两者所花费的时间中,文件顶部的任何一个都比另一个要花费更多的时间

例如:如果我在一开始为循环编写代码,那么它比递归要花更多的时间,反之亦然。在这两个过程中所花费的时间相差30到40倍

我的问题是:

循环和递归的顺序重要吗? 有与印刷有关的东西吗? 这种行为的可能原因是什么? 下面是我在同一个文件中的代码,我使用的语言是scala

  def count(x: Int): Unit = {
    if (x <= 1000) {
      print(s"$x ")
      count(x + 1)
    }
  }

  val t3 = System.currentTimeMillis()
  count(1)
  val t4 = System.currentTimeMillis()
  println(s"\ntime taken by the recursion look = ${t4 - t3} mili second")

  var c = 1

  val t1 = System.currentTimeMillis()
  while(c <= 1000)
    {
      print(s"$c ")
      c+=1
    }
  val t2 = System.currentTimeMillis()

  println(s"\ntime taken by the while loop = ${t2 - t1} mili second")
在这种情况下,递归和while循环所需的时间分别为986ms和20ms

当我切换循环和递归的位置时,即先循环再递归,递归和while循环所用的时间分别为1.69秒和28毫秒

编辑1:
如果递归代码位于顶部,我可以看到bufferWriter的相同行为。但当递归低于循环时,情况并非如此。当递归低于循环时,它所用的时间几乎相同,相差2到3毫秒。

Scala不会编译成机器代码,而是编译成Java虚拟机JVM的字节码,然后在本机处理器上解释该代码。JVM使用多种机制优化频繁运行的代码,最终将频繁调用的函数热点转换为纯机器代码

这意味着测试函数的第一次运行并不能很好地衡量最终性能。在尝试测量所花费的时间之前,需要通过多次运行测试代码来预热JIT编译器


此外,如评论中所述,执行任何类型的I/O都会使计时变得非常不可靠,因为存在I/O阻塞的危险。如果可能,编写一个不执行任何阻塞的测试用例。

Scala不会编译成Java虚拟机JVM的机器代码,而是编译成字节码,然后在本机处理器上解释该代码。JVM使用多种机制优化频繁运行的代码,最终将频繁调用的函数热点转换为纯机器代码

这意味着测试函数的第一次运行并不能很好地衡量最终性能。在尝试测量所花费的时间之前,需要通过多次运行测试代码来预热JIT编译器


此外,如评论中所述,执行任何类型的I/O都会使计时变得非常不可靠,因为存在I/O阻塞的危险。如果可能的话,编写一个不进行任何阻塞的测试用例。

如果您想让自己相信tailrec优化是有效的,而不依赖于任何分析工具,下面是您可以尝试的:

使用更多迭代的方法 扔掉前几次迭代,让JIT有时间醒来并进行热点优化 扔掉所有不可预知的副作用,比如打印到标准输出 扔掉所有昂贵的操作,这两种方法在格式化数字等方面都是一样的。 多轮测量 随机化每轮的重复次数 随机化每轮中变量的顺序,以避免与垃圾收集器的周期发生灾难性的共振 最好不要在计算机上运行任何其他操作 大致如下:

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循环和tail递归函数所用的时间大致相同。

如果您想让自己相信tailrec优化是有效的,而不依赖于任何分析工具,下面是您可以尝试的:

使用更多迭代的方法 扔掉前几次迭代,让JIT有时间醒来并进行热点优化 扔掉所有不可预知的副作用,比如打印到标准输出 扔掉所有昂贵的操作,这两种方法在格式化数字等方面都是一样的。 多轮测量 随机化每轮的重复次数 随机化每轮中变量的顺序,以避免与垃圾收集器的周期发生灾难性的共振 最好不要在计算机上运行任何其他操作 大致如下:

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循环和tail递归函数所用的时间大致相同。

这个问题没有一般的答案,因为编译器也在发挥神奇的作用,有时递归函数展开到循环中的内容请参见tail recursion。@AndreasNeumann这取决于什么?你能告诉我吗?使用BufferedWriter时也会有时差。所以,我们不能因为这件事责怪print或println
这个问题没有一般性的答案,因为编译器也在发挥神奇的作用,有时递归函数会被展开成一个循环,请参见尾部递归。@AndreasNeumann那么它取决于什么呢?你能告诉我吗?使用BufferedWriter时也会有时差。所以,我们不能把这归咎于print或println。