2010-10-18 27 views
7

Tôi sẽ hỏi điều này với một ví dụ Scala, nhưng nó cũng có thể là điều này ảnh hưởng đến các ngôn ngữ khác cho phép lai phong cách bắt buộc và chức năng.Làm cách nào để tránh vô tình nắm bắt phạm vi địa phương trong các hàm chức năng?

Dưới đây là một ví dụ ngắn (CẬP NHẬT, xem dưới đây):

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

Bây giờ iterator.filterNot công trình một cách lười biếng, đó là rất tốt! Kết quả là, chúng tôi hy vọng rằng trình lặp trở lại sẽ không tiêu thụ nhiều bộ nhớ (thực sự, O (1)). Đáng buồn thay, tuy nhiên, chúng tôi đã thực hiện một sai lầm khủng khiếp: vì filterNot là lười biếng, nó giữ một tham chiếu đến hàm số huge contains _. Vì vậy, trong khi chúng tôi nghĩ rằng phương pháp này sẽ đòi hỏi một lượng lớn bộ nhớ trong khi nó đang chạy, và bộ nhớ đó có thể được giải phóng ngay sau khi kết thúc phương pháp, trên thực tế bộ nhớ bị kẹt cho đến khi chúng ta quên Iterator.

(Tôi chỉ cần thực hiện như một sai lầm, mà mất một thời gian dài để theo dõi xuống! Bạn có thể bắt những việc như vậy nhìn vào đống bãi ...)

thực hành tốt nhất để tránh vấn đề này là gì?

Dường như giải pháp duy nhất là kiểm tra cẩn thận các hàm chức năng tồn tại ở cuối phạm vi và bắt các biến trung gian. Đây là một chút khó xử nếu bạn đang xây dựng một bộ sưu tập không nghiêm ngặt và lập kế hoạch trả lại nó. Bất cứ ai có thể nghĩ về một số thủ thuật tốt đẹp, Scala cụ thể hoặc cách khác, mà tránh vấn đề này và cho tôi viết mã đẹp?

CẬP NHẬT: ví dụ tôi đã đưa ra trước đó là ngu ngốc, như câu trả lời của huynhjl dưới đây minh họa. Nó đã là:

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 
} 

Thực tế, bây giờ tôi đã hiểu rõ hơn về những điều này hoạt động như thế nào, tôi không quá lo lắng!

Trả lời

5

Bạn có chắc là bạn không đơn giản hóa trường hợp kiểm tra không? Dưới đây là những gì tôi chạy:

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

Nếu tôi hiểu bạn một cách chính xác, bởi vì main là sử dụng vòng lặp ngay cả sau khi gc() cuộc gọi, JVM sẽ được tổ chức vào huge đối tượng.

Đây là cách tôi chạy nó:

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

Đây là những gì nó in phía cuối:

[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 

Vì vậy, có vẻ với tôi như thể các đối tượng được khai hoang huge ...

+0

Huh, quả thật vậy! Tôi sẽ cố gắng đưa ra một ví dụ tốt hơn vào ngày mai, nhưng bây giờ tôi đang bối rối * tại sao * bạn đang nhìn thấy những gì bạn đã làm. Bất cứ ai có thể đưa ra một bản tóm tắt tốt về các biến cục bộ nào được chụp bởi hàm đó theo nghĩa đen? –

Các vấn đề liên quan