Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/scala/16.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
Performance 为什么';t尾递归在这段代码中会产生更好的性能吗?_Performance_Scala_Benchmarking - Fatal编程技术网

Performance 为什么';t尾递归在这段代码中会产生更好的性能吗?

Performance 为什么';t尾递归在这段代码中会产生更好的性能吗?,performance,scala,benchmarking,Performance,Scala,Benchmarking,我正在创建一个更快的字符串拆分器方法。首先,我编写了一个返回List的非尾部递归版本。接下来,使用ListBuffer然后调用toList(+=和toList是O(1))的尾部递归。我完全期望尾部递归版本更快,但事实并非如此 有人能解释为什么吗 原文: def split(s: String, c: Char, i: Int = 0): List[String] = if (i < 0) Nil else { val p = s indexOf (c, i) if (p <

我正在创建一个更快的字符串拆分器方法。首先,我编写了一个返回
List
的非尾部递归版本。接下来,使用
ListBuffer
然后调用
toList
+=
toList
是O(1))的尾部递归。我完全期望尾部递归版本更快,但事实并非如此

有人能解释为什么吗

原文:

def split(s: String, c: Char, i: Int = 0): List[String] = if (i < 0) Nil else {
  val p = s indexOf (c, i)
  if (p < 0) s.substring(i) :: Nil else s.substring(i, p) :: split(s, c, p + 1)
}
def split(s:String,c:Char,i:Int=0):List[String]=if(i<0)Nil else{
val p=s指数f(c,i)
如果(p<0)s子串(i)::无其他s子串(i,p)::拆分(s,c,p+1)
}
尾递归一:

import scala.annotation.tailrec
import scala.collection.mutable.ListBuffer
def split(s: String, c: Char): Seq[String] = {
  val buffer = ListBuffer.empty[String]
  @tailrec def recurse(i: Int): Seq[String] =  {
    val p = s indexOf (c, i)
    if (p < 0) {
      buffer += s.substring(i)
      buffer.toList
    } else {
      buffer += s.substring(i, p)
      recurse(p + 1)
    }
  }
  recurse(0)
}
import scala.annotation.tailrec
导入scala.collection.mutable.ListBuffer
def拆分(s:String,c:Char):Seq[String]={
val buffer=ListBuffer.empty[字符串]
@tailrec def recurse(i:Int):Seq[String]={
val p=s指数f(c,i)
if(p<0){
缓冲区+=s.子串(i)
缓冲区列表
}否则{
缓冲区+=s.子串(i,p)
递归(p+1)
}
}
递归(0)
}

scala的jyxent用代码和结果对其进行了基准测试。

在第二种情况下,您只是做了更多的工作。在第一种情况下,您可能会使堆栈溢出,但每个操作都非常简单,
是尽可能小的包装(您所要做的就是创建包装并将其指向另一个列表的头部)。在第二种情况下,不仅您最初创建了一个额外的集合,并且必须围绕嵌套方法使用的
s
buffer
形成一个闭包,而且您还使用了更重的
ListBuffer
,它必须检查每个
+=
是否已经被复制到列表中,并根据是否为空使用不同的代码路径(以便将
O(1)
附加到工作中)。

由于尾部调用优化,您希望尾部递归版本更快,我认为这是正确的,如果您将苹果与苹果进行比较:

def split3(s: String, c: Char): Seq[String] = {
  @tailrec def recurse(i: Int, acc: List[String] = Nil): Seq[String] =  {
    val p = s indexOf (c, i)
    if (p < 0) {
      s.substring(i) :: acc
    } else {
      recurse(p + 1, s.substring(i, p) :: acc)
    }
  }
  recurse(0) // would need to reverse
}

这有尾部调用优化,并避免了
ListBuffer

@pst不,
ListBuffer
+=
确实是O(1)。我猜这与ListBuffer的使用有关,而不是使用尾部递归。仅仅因为这些操作是O(1),并不意味着它们一定和cons一样快。它们只是在它的速度的一个常数范围内。您是否做过任何测试来尝试建立ListBuffer与cons操作的基线性能?GC开销是否会有所不同?为什么尾部递归版本会明显更快?它不会占用堆栈空间,这很酷,但工作似乎相当。我没有考虑<代码> s>代码>和<代码>缓冲区< /代码>闭包。我想知道如果我把它们作为参数,它是否会更快。。。现在我想起来了,另一方面,我不必从
recurse
返回
buffer
——我可以在
split
结束时返回
buffer
。我认为你是对的——这是我的怀疑,尽管你对闭包提出了一个非常有趣的观点。然而,我将接受huynhjl的答案,因为他不厌其烦地证明,如果所有其他事实都相同,尾部递归确实会带来性能提升。哦,当我说“我认为你是正确的”时,我并不怀疑你说的任何话。我只是想知道,当我问这个问题时,我是否遗漏了其他东西(比如闭包)。我想我们可能还是遗漏了什么。
def split3(s: String, c: Char): Seq[String] = {
  @tailrec def recurse(i: Int, acc: List[String] = Nil): Seq[String] =  {
    val p = s lastIndexOf (c, i)
    if (p < 0) {
      s.substring(0, i + 1) :: acc
    } else {
      recurse(p - 1, s.substring(p + 1, i + 1) :: acc)
    }
  }
  recurse(s.length - 1)
}