2010-10-18 4 views
7

Lo chiederò con un esempio di Scala, ma potrebbe essere che ciò influenzi altri linguaggi che consentono stili imperativi e funzionali ibridi.Come evitare di catturare involontariamente l'ambito locale nei valori letterali delle funzioni?

Ecco un breve esempio (AGGIORNATO, vedi sotto):

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

Ora iterator.filterNot opere pigramente, che è grande! Di conseguenza, ci aspetteremmo che l'iteratore restituito non consumerà molta memoria (infatti, O (1)). Purtroppo, tuttavia, abbiamo commesso un terribile errore: poiché filterNot è pigro, mantiene un riferimento alla funzione letterale huge contains _.

Così mentre pensavamo che il metodo richiedesse una grande quantità di memoria mentre era in esecuzione, e che quella memoria potesse essere liberata immediatamente dopo la fine del metodo, infatti quella memoria è bloccata fino a quando non dimentichiamo il reso Iterator.

(Ho appena fatto un tale errore, che ha avuto molto tempo per rintracciare! Si può prendere queste cose guardando mucchio discariche ...)

Quali sono le migliori pratiche per evitare questo problema?

Sembra che l'unica soluzione sia controllare attentamente i valori letterali delle funzioni che sopravvivono alla fine dell'ambito e quali hanno catturato le variabili intermedie. Questo è un po 'imbarazzante se stai costruendo una raccolta non rigida e pianifichi di restituirla. Qualcuno può pensare a qualche bel trucco, specifico per Scala o altro, che eviti questo problema e mi permetta di scrivere un bel codice?

AGGIORNAMENTO: l'esempio che ho fornito in precedenza era stupido, come dimostra la risposta di huynhjl qui sotto. Era stato:

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 
} 

Infatti, ora che ho capito un po 'meglio come funzionano queste cose, io non sono così preoccupato!

risposta

5

Sei sicuro di non semplificare troppo il test case? Ecco cosa ho eseguito:

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

Se ho capito bene, perché main sta usando l'iteratori anche dopo la chiamata gc(), la JVM sarebbe trattenendo gli oggetti huge.

Questo è come l'eseguo:

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

Questo è ciò che la stampa verso la fine:

[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 

Così sembra a me come se gli oggetti sono stati recuperati huge ...

+0

Huh, davvero! Cercherò di trovare un esempio migliore domani, ma per ora sono confuso * perché * stai vedendo quello che hai fatto. Qualcuno può dare un buon riassunto di quali variabili locali sono catturate da quella funzione letterale? –

Problemi correlati