2011-06-12 12 views
8

Estoy implementando algunos algoritmos que funcionan en datos grandes (~ 250 MB - 1 GB). Para esto, necesitaba un ciclo para hacer algunos benchmarking. Sin embargo, en el proceso, me doy cuenta de que F # está haciendo cosas desagradables, que espero que algunos de ustedes puedan aclarar.Compilador F # mantiene vivos los objetos muertos

Aquí está mi código (descripción del problema se encuentra por debajo):

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 

Aquí la salida será como:

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

Out of memory exception 

Así, en el circuito de F # descarta el resultado, pero cuando No estoy al corriente F # guardará referencias a "datos muertos" (he buscado en el IL, y aparentemente el programa de la clase obtiene campos para estos datos). ¿Por qué? ¿Y puedo arreglar eso?

Este código se ejecuta fuera de Visual Studio y en modo de lanzamiento.

Respuesta

17

El motivo de este comportamiento es que el compilador F # se comporta de forma diferente en el ámbito global que en el ámbito local. Una variable declarada en el alcance global se convierte en un campo estático. Una declaración de módulo es una clase estática con declaraciones let compiladas como campos/propiedades/métodos.

La forma más sencilla de solucionar el problema es escribir el código en una función:

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

... pero ¿por qué declarar el compilador campos cuando no se está utilizando el valor y justo ignore ella? Esto es bastante interesante: la función ignore es una función muy simple que está insertada cuando la usa. La declaración es let inline ignore _ =(). Al alinear la función, el compilador declara algunas variables (para almacenar los argumentos de la función).

Por lo tanto, otra manera de solucionar este problema es omitir ignore y escribir:

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

que obtendrá algunas advertencias del compilador, ya que el resultado de la expresión no es unit, pero funcionará. Sin embargo, usar alguna función y escribir código en el ámbito local es probablemente más confiable.

+0

+1 Gracias, interesante :) La primera solución funciona, pero ignorar ignorar no ayudó aquí. Todavía estoy interesado en descubrir por qué hace lo que hace, sin embargo. –

+1

Interesante ... Me ayudó cuando probé con la opción '-O' (para habilitar optimizaciones). –

+0

Extraño. Funcionó aquí también cuando lo ejecuté fuera de Visual Studio. Sin embargo, ahora el bucle dio como resultado una excepción de falta de memoria, pero SÓLO si primero había ejecutado la versión "sin bucle":/Supongo que me limitaré al alcance local. –

Cuestiones relacionadas