为什么headOption更快

时间:2013-07-31 03:37:05

标签: scala optimization queue option

我对一些代码进行了更改,速度提高了4.5倍。我想知道为什么。它本质上是:

def doThing(queue: Queue[(String, String)]): Queue[(String, String)] = queue match {
  case Queue((thing, stuff), _*) => doThing(queue.tail)
  case _ => queue
}

我把它改成了这个以获得巨大的速度提升:

def doThing(queue: Queue[(String, String)]): Queue[(String, String)] = queue.headOption match {
  case Some((thing, stuff)) => doThing(queue.tail)
  case _ => queue
}

与{head}相比,_*做了什么,为什么这么贵?

3 个答案:

答案 0 :(得分:4)

我在使用-Xprint:all运行scalac后的猜测是,在queue match { case Queue((thing, stuff), _*) => doThing(queue.tail) }示例中 patmat 的末尾,我看到调用了以下方法(为简洁起见而编辑):< / p>

val o9 = scala.collection.immutable.Queue.unapplySeq[(String, String)](x1);
if (o9.isEmpty.unary_!)
  if (o9.get.!=(null).&&(o9.get.lengthCompare(1).>=(0)))
    {
      val p2: (String, String) = o9.get.apply(0);
      val p3: Seq[(String, String)] = o9.get.drop(1);

所以lengthCompare以可能优化的方式比较集合的长度。对于Queue,它创建一个迭代器并迭代一次。所以这应该有点快。另一方面,drop(1)也构造一个迭代器,跳过一个元素并将其余元素添加到结果队列中,因此它在集合的大小上是线性的。

headOption示例更简单,它检查列表是否为空(两次比较),如果不是则返回Some(head),然后只有_1和{{} 1}}分配给_2thing。因此,没有创建迭代器,并且集合的长度没有线性。

答案 1 :(得分:2)

您的代码示例之间应该没有显着差异。

case Queue((thing, stuff), _*)实际上是由编译器翻译为调用headapply(0))方法。您可以使用scalac -Xprint:patmat对此进行调查:

<synthetic> val p2: (String, String) = o9.get.apply(0);
if (p2.ne(null))
  matchEnd6(doThing(queue.tail))

head的费用和headOption的费用几乎相同。

方法headtaildequeue可能会reverce List Queue O(n)(费用为reverce) 。在两个代码示例中,最多会有2 dequeue次调用。 您应该像这样使用reverce来获得最多一次def doThing(queue: Queue[(String, String)]): Queue[(String, String)] = if (queue.isEmpty) queue else queue.dequeue match { case (e, q) => doThing(q) } 电话:

(thing, stuff)

您也可以将_替换为lengthCompare。在这种情况下,编译器将仅生成head tailif (o9.get != null && o9.get.lengthCompare(1) >= 0) 的调用:

{{1}}

答案 2 :(得分:0)

_*用于指定varargs参数,因此您在第一个版本中所做的是将Queue解构为一对字符串,以及适当数量的其他字符串对 - 即您正在解构整个即使你只关心第一个元素,也要排队。

如果您只是删除星号,请提供

def doThing(queue: Queue[(String, String)]): Queue[(String, String)] = queue match {
  case Queue((thing, stuff), _) => doThing(queue.tail)
  case _ => queue
}

然后你只是将队列解构为一对字符串和一个余数(因此不需要完全解构)。这应该在与你的第二个版本相当的时间内运行(尽管我自己没有计时)。