2013-07-31 46 views
4

我對某些代碼進行了更改,它的速度提高了4.5倍。我想知道爲什麼。它曾經是本質:爲什麼headOption更快

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 
} 

是什麼_*做,爲什麼它如此昂貴相比headOption?

+0

這是可變的'隊列'還是不可變的? – huynhjl

+0

@huynhjl,不可變 – kelloti

+2

什麼是您的編譯器版本? – senia

回答

3

-Xprint:all運行scalac後,我的猜測是,在patmatqueue match { case Queue((thing, stuff), _*) => doThing(queue.tail) }例子結束時,我看到被調用下面的方法(編輯爲簡潔起見):

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_2分配給thingstuff。所以沒有創建迭代器,並且集合的長度沒有任何線性。

+0

什麼是您的編譯器版本?我使用'2.10.1'和​​'2.11.0-M3',並且沒有'p3'。 – senia

+0

@senia,2.10.0。你正在檢查*不可變*隊列嗎? – huynhjl

+0

@senia,我明白你的意思了,我確實看到'o9.get.apply(0)'有更新的版本,沒有'drop'。 – huynhjl

0

_*用於指定可變參數參數,因此您在第一個版本中所做的是將隊列解構爲一對字符串以及適當數量的其他字符串 - 即使您正在解構整個隊列你只關心第一個元素。

如果你只是刪除了星號,給

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

那麼你只解構隊列進入一對字符串,和餘數(這因此並不需要完全解構)。這應該在與第二個版本相當的時間內運行(儘管我自己也沒有計時)。

+0

'隊列((東西,東西),_)'是收集** 2 **元素的模式。 – senia

+0

@senia哎呀,你是對的 - 我在想Queue是像List一樣構建的。然而,圍繞爲什麼'_ *'需要這麼長時間的解構主義論證仍然適用(但堅持使用headOption或出列或類似的方法)。 – Shadowlands

+0

'Queue'實際上是使用2個列表構造的。但是'List(a,b)'也是2個元素列表的模式。 – senia

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可以在(與成本O(n))內部QueueList導致reverce。在這兩個代碼示例中,最多隻能有2個reverce調用。 你應該使用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呼叫而不headtail

if (o9.get != null && o9.get.lengthCompare(1) >= 0) 
+0

謝謝!我也會嘗試。但爲什麼我的例子中的差異? – kelloti

+0

@kelloti:你確定這兩個例子的表現不一樣嗎?你的代碼可以被jit優化爲無用的。從'scalac -Xprint:patmat'可以看出,你的2個代碼樣本之間沒有顯着差異。 – senia

+0

@senia,我不認爲'head'和'tail'是O(n)。請參閱https://github.com/scala/scala/blob/v2.10.2/src/library/scala/collection/immutable/Queue.scala#L81。它真的利用了結構共享,對我來說似乎是O(1)。對不起,我把O(1)拿回來,裏面有一個'in.reverse'。但'dequeue'也是如此。 – huynhjl