2011-06-12 8 views
8

Sto implementando alcuni algoritmi che funzionano su dati di grandi dimensioni (~ 250 MB - 1 GB). Per questo avevo bisogno di un ciclo per fare un po 'di benchmarking. Tuttavia, nel processo apprendo che F # sta facendo alcune cose sgradevoli, che spero che alcuni di voi possano chiarire.Compilatore F # mantiene in vita gli oggetti morti

Ecco il mio codice (descrizione del problema è al di sotto):

open System 

for i = 1 to 10 do 
    Array2D.zeroCreate 10000 10000 |> ignore  
    printfn "%d" (GC.GetTotalMemory(true)) 

Array2D.zeroCreate 10000 10000 |> ignore 
// should force a garbage collection, and GC.Collect() doesn't help either 
printfn "%d" (GC.GetTotalMemory(true)) 
Array2D.zeroCreate 10000 10000 |> ignore  
printfn "%d" (GC.GetTotalMemory(true)) 
Array2D.zeroCreate 10000 10000 |> ignore  
printfn "%d" (GC.GetTotalMemory(true)) 
Array2D.zeroCreate 10000 10000 |> ignore  
printfn "%d" (GC.GetTotalMemory(true)) 

Console.ReadLine() |> ignore 

Ecco l'output sarà simile:

54000 
54000 
54000 
54000 
54000 
54000 
54000 
54000 
54000 
54000 
400000000 
800000000 
1200000000 

Out of memory exception 

Così, nel circuito di F # scarta il risultato, ma quando Non sono nel ciclo F # manterrà i riferimenti ai "dati morti" (ho cercato nell'IL, e apparentemente la classe Program ottiene i campi per questi dati). Perché? E posso ripararlo?

Questo codice viene eseguito all'esterno di Visual Studio e in modalità di rilascio.

risposta

17

Il motivo di questo comportamento è che il compilatore F # si comporta in modo diverso nell'ambito globale rispetto all'ambito locale. Una variabile dichiarata nell'ambito globale viene trasformata in un campo statico. Una dichiarazione di modulo è una classe statica con le dichiarazioni let compilate come campi/proprietà/metodi.

Il modo più semplice per risolvere il problema è quello di scrivere il codice in una funzione:

let main() =  
    Array2D.zeroCreate 10000 10000 |> ignore  
    printfn "%d" (GC.GetTotalMemory(true)) 
    Array2D.zeroCreate 10000 10000 |> ignore  
    printfn "%d" (GC.GetTotalMemory(true)) 
    // (...) 
    Console.ReadLine() |> ignore 

main() 

... ma perché il compilatore dichiara campi quando non si sta utilizzando il valore e basta ignore esso? Questo è piuttosto interessante: la funzione ignore è una funzione molto semplice che è inarcata quando la usi. La dichiarazione è let inline ignore _ =(). Quando si incorpora la funzione, il compilatore dichiara alcune variabili (per memorizzare gli argomenti della funzione).

Quindi, un altro modo per risolvere questo problema è quello di omettere ignore e scrivere:

Array2D.zeroCreate 10000 10000 
printfn "%d" (GC.GetTotalMemory(true)) 
Array2D.zeroCreate 10000 10000 
printfn "%d" (GC.GetTotalMemory(true)) 
// (...) 

Otterrete alcuni avvisi del compilatore, perché il risultato di espressione non è unit, ma funzionerà. Tuttavia, l'utilizzo di alcune funzioni e la scrittura di codice in ambito locale è probabilmente più affidabile.

+0

+1 Grazie, interessante :) La prima soluzione funziona, ma ignorare ignorare non ha aiutato qui. Sono ancora interessato a scoprire perché fa quello che fa, però. –

+1

Interessante ... Mi ha aiutato quando l'ho provato con l'opzione '-O' (per abilitare le ottimizzazioni). –

+0

Strano. Ha funzionato anche qui quando l'ho eseguito al di fuori di Visual Studio. Tuttavia, ora il ciclo ha provocato un'eccezione fuori memoria, ma SOLO se avessi eseguito prima la versione "non-loop":/Immagino che mi limiterò ad attenermi all'ambito locale. –

Problemi correlati