2016-09-25 45 views
0

我對Scala如何使用StreamList跨轉換分配內存有疑問。讓我們舉一個簡單的例子是這樣使用寫入兩個Stream/ListScala:跨轉換的Stream和List的內存使用情況

Stream(1,2,3,4,5).map(_ + 3).filter (_ % 2 ==0).toList 

List(1,2,3,4,5).map(_ + 3).filter (_ % 2 ==0) 

使用List(1,2,3,4,5)會在內存中分配第一個新的「結構」(我們假設另一位List)後map然後又List過濾器後,對於共2個List s,加上爲每個列表分配了包含的元素?

Stream的情況下它只會是一個嗎?

如果兩者均爲「是」,那麼爲什麼Scala不將Stream行爲用作轉換的默認行爲?

回答

2

懶惰評估使得它很難推論,當事情進行評估和懶惰沒有副作用以及工作

惰性列表的內存消耗(一般懶惰的數據結構)是很難對付因爲它們會導致記憶中未被評估的thunk的積累。

懶惰不適合副作用。在副作用的情況下,副作用何時發生在計算問題上。由於評估推遲,副作用的時間很難推理。例如,當全局變量被讀取/寫入時,它很重要。當db寫/讀發生並且打印到控制檯時,它很重要

Thunk表示延遲計算被評估或未評估的表達式。

懶惰流(或懶惰列表)導致佔用內存的thunk(計算描述)。除非明確要求,否則這些thunk不會被評估。在程序執行過程中,內存消耗可能會非常高,因爲未被評估的thunk在內存中累積。這也是haskell列表的經典問題。默認情況下,haskell是懶惰的。這就是爲什麼有一個嚴格的摺疊版本和哈希克爾摺疊的懶惰版本來處理這類問題。

讓檢查與Stream代碼

這裏是過濾器,從標準庫咚tl

/** A lazy cons cell, from which streams are built. */ 
    @SerialVersionUID(-602202424901551803L) 
    final class Cons[+A](hd: A, tl: => Stream[A]) extends Stream[A] { 
    override def isEmpty = false 
    override def head = hd 
    @volatile private[this] var tlVal: Stream[A] = _ 
    @volatile private[this] var tlGen = tl _ 
    def tailDefined: Boolean = tlGen eq null 
    override def tail: Stream[A] = { 
     if (!tailDefined) 
     synchronized { 
      if (!tailDefined) { 
      tlVal = tlGen() 
      tlGen = null 
      } 
     } 

     tlVal 
    } 
    } 

tl是代碼

override def filter(p: A => Boolean): Stream[A] = { 
    // optimization: drop leading prefix of elems for which f returns false 
    // var rest = this dropWhile (!p(_)) - forget DRY principle - GC can't collect otherwise 
    var rest = this 
    while (!rest.isEmpty && !p(rest.head)) rest = rest.tail 
    // private utility func to avoid `this` on stack (would be needed for the lazy arg) 
    if (rest.nonEmpty) Stream.filteredTail(rest, p) 
    else Stream.Empty 
    } 

private[immutable] def filteredTail[A](stream: Stream[A], p: A => Boolean) = { 
    cons(stream.head, stream.tail filter p) 
    } 

這裏是懶洋洋地評估發生了什麼大塊在內存中一直存在ing會觸發它的評估。 Thunk可能還會產生多個其他的thunk,這些thunk可能會保持未評估的狀態並佔用內存。

Stream(1, 2, 3, 4, 5, 6).filter(_ % 2 == 0) 

cons(2, thunk) 

cons(2, cons(4, thunk) 

cons(2, 4, 6, thunk) 
+0

有什麼規則或建議什麼時候更喜歡一個或另一個?是否總是需要測量它們? – Randomize

+0

是的,有很多地方「懶惰」比嚴格的評估表現更好。但它很好地測試大輸入的懶惰程序來檢查它們的內存腳印 – pamu

+0

@pamu:感謝您的詳細解釋。這是有幫助的。 – Samar