2010-10-18 9 views
7

我會問一個Scala的例子,但很可能這會影響到其他語言,這些語言允許混合命令式和功能樣式。我應該如何避免無意中捕獲函數文字中的局部範圍?

下面是一個簡單的例子(修訂,見下文):

def method: Iterator[Int] { 
    // construct some large intermediate value 
    val huge = (1 to 1000000).toList   
    val small = List.fill(5)(scala.util.Random.nextInt) 
    // accidentally use huge in a literal 
    small.iterator filterNot (huge contains _)  
} 

現在iterator.filterNot懶洋洋的作品,這是偉大的!因此,我們期望返回的迭代器不會消耗太多內存(實際上,O(1))。但不幸的是,我們已經有了一個可怕的錯誤:因爲filterNot是懶惰的,它保持對函數文本huge contains _參考。

因此,雖然我們認爲該方法在運行時需要大量內存,並且該方法終止後該內存可以立即釋放,但事實上內存會卡住,直到我們忘記返回Iterator

(我只是做了這樣的錯誤,這花了很長的時間來追查!你能趕上這樣的事情望着堆轉儲...)

什麼是避免這個問題的最佳做法?

看來,唯一的解決方案是仔細檢查在範圍末尾倖存的函數文字以及捕獲的中間變量。如果你正在構建一個非嚴格的集合並計劃返回它,這有點尷尬。任何人都可以考慮一些不錯的技巧,Scala特有的或其他的,以避免這個問題,讓我寫出漂亮的代碼?

更新:我以前給出的例子很愚蠢,因爲huynhjl的回答如下所示。它一直:

def method: Iterator[Int] { 
    val huge = (1 to 1000000).toList // construct some large intermediate value 
    val n = huge.last    // do some calculation based on it 
    (1 to n).iterator map (_ + 1) // return some small value 
} 

事實上,現在我明白了一個好一點的這些東西是如何工作的,我不是很擔心!

回答

5

你確定你不是簡單的測試用例嗎?以下是我運行:

object Clos { 
    def method: Iterator[Int] = { 
    val huge = (1 to 2000000).toList 
    val n = huge.last 
    (1 to n).iterator map (_ + 1) 
    } 

    def gc() { println("GC!!"); Runtime.getRuntime.gc } 

    def main(args:Array[String]) { 
    val list = List(method, method, method) 
    list.foreach(m => println(m.next)) 
    gc() 
    list.foreach(m => println(m.next)) 
    list.foreach(m => println(m.next)) 
    } 
} 

如果我理解正確的話,因爲main是使用迭代器甚至gc()通話結束後,JVM將舉行到huge對象。

這是我如何運行它:

JAVA_OPTS="-verbose:gc" scala -cp classes Clos 

這是它接近尾聲打印:

[Full GC 57077K->57077K(60916K), 0.3340941 secs] 
[Full GC 60852K->60851K(65088K), 0.3653304 secs] 
2 
2 
2 
GC!! 
[Full GC 62959K->247K(65088K), 0.0610994 secs] 
3 
3 
3 
4 
4 
4 

所以看起來我好像huge對象被回收...

+0

咦,的確如此!我會在明天嘗試提出一個更好的例子,但現在我很困惑*爲什麼*你看到你做了什麼。任何人都可以給出一個很好的總結,哪些局部變量被該函數文字捕獲? – 2010-10-18 06:59:05