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)
}