2011-06-12 14 views
18

Teniendo en cuenta el programa:¿Por qué performGC no libera toda la memoria?

import Language.Haskell.Exts.Annotated -- from haskell-src-exts 
import System.Mem 
import System.IO 
import Control.Exception 

main :: IO() 
main = do 
    evaluate $ length $ show $ fromParseResult $ parseFileContents $ "data C = C {a :: F {- " ++ replicate 400000 'd' ++ " -}  }" 
    performGC 
    performGC 
    performGC 

Usando GHC 7.0.3, cuando corro:

$ ghc --make Temp.hs -rtsopts && Temp.exe +RTS -G1 -S 
    Alloc Copied  Live GC GC  TOT  TOT Page Flts 
    bytes  bytes  bytes user elap user elap 
... 
29463264  64 8380480 0.00 0.00 0.64 0.85 0 0 (Gen: 0) 
     20  56 8380472 0.00 0.00 0.64 0.86 0 0 (Gen: 0) 
     0  56 8380472 0.00 0.00 0.64 0.87 0 0 (Gen: 0) 
    42256  780  33452 0.00 0.00 0.64 0.88 0 0 (Gen: 0) 
     0      0.00 0.00 

La llamada performGC parece dejar de 8 Mb de memoria viva, a pesar de que parece que toda la memoria debe muerete. ¿Cómo?

(Sin -G1 veo 10Mb vivo al final, lo que tampoco puedo explicar.)

+11

Soy consciente de que esto es un comentario totalmente inútil, pero que realmente me gusta ver a preguntas aquí de personas que saben mucho más acerca de Haskell que yo, porque las respuestas que siguen tienden a ser completamente fascinantes. :] –

+3

Bueno, es un cambio agradable del flujo constante de preguntas que responden por "do not, pero' insafePerformIO' "," 'fromIntegral'" y "convertir pestañas en espacios" ... – yatima2975

Respuesta

18

Aquí es lo que veo (después de insertar un print antes de la última performGC, para ayudar a la etiqueta cuando las cosas suceden

524288 524296 32381000 0.00 0.00 1.15 1.95 0 0 (Gen: 0) 
    524288 524296 31856824 0.00 0.00 1.16 1.96 0 0 (Gen: 0) 
    368248  808 1032992 0.00 0.02 1.16 1.99 0 0 (Gen: 1) 
     0  808 1032992 0.00 0.00 1.16 1.99 0 0 (Gen: 1) 
"performed!" 
    39464  2200 1058952 0.00 0.00 1.16 1.99 0 0 (Gen: 1) 
    22264  1560 1075992 0.00 0.00 1.16 2.00 0 0 (Gen: 0) 
     0      0.00 0.00 

Así que después de GC todavía hay 1M en el montón (sin -G1) Con.. - G1 veo:.

34340656 20520040 20524800 0.10 0.12 0.76 0.85 0 0 (Gen: 0) 
41697072 24917800 24922560 0.12 0.14 0.91 1.01 0 0 (Gen: 0) 
70790776  800 2081568 0.00 0.02 1.04 1.20 0 0 (Gen: 0) 
     0  800 2081568 0.00 0.00 1.04 1.20 0 0 (Gen: 0) 
"performed!" 
    39464  2184 1058952 0.00 0.00 1.05 1.21 0 0 (Gen: 0) 
    22264  2856  43784 0.00 0.00 1.05 1.21 0 0 (Gen: 0) 
     0      0.00 0.00 

Así aproximadamente 2 M Este es el x86_64/Linux

Vamos a pensar en the STG machine storage model para ver si hay algo. más en el montón.

cosas que podrían ser en ese 1M del espacio:

  • CAF para cosas como [], las constantes de cadena, y el pequeño Int y Char piscina, además de las cosas en las bibliotecas, la stdin MVar?
  • Thread State Objects (TSO) para el subproceso main.
  • Cualquier manejador de señal asignado.
  • El administrador de IO código Haskell.
  • chispas en la piscina chispa

Por experiencia, esta cifra ligeramente inferior a 1 M parece ser la "huella" por defecto de un binario GHC. Eso es lo que también he visto en otros programas (por ejemplo, las huellas más pequeñas del programa de tiroteos nunca son inferiores a 900K).

Quizás el generador de perfiles puede decir algo.Aquí está la -hT perfil (no hay bibliotecas de perfiles necesarios), después de insertar un bucle ocupado mínimo al final de la cadena a cabo la cola:

$ ./A +RTS -K10M -S -hT -i0.001  

Los resultados en este gráfico:


enter image description here


Victory! ¡Mira ese ~ 1M objeto de pila de hilos ahí!

No conozco una forma de reducir los TSO.


El código que produce el gráfico anterior:

import Language.Haskell.Exts.Annotated -- from haskell-src-exts 
import System.Mem 
import System.IO 
import Data.Int 
import Control.Exception 

main :: IO() 
main = do 
    evaluate $ length $ show $ fromParseResult 
      $ parseFileContents 
      $ "data C = C {a :: F {- " ++ replicate 400000 'd' ++ " -}  }" 
    performGC 
    performGC 
    print "performed!" 
    performGC 

    -- busy loop so we can sample what's left on the heap. 
    let go :: Int32 -> IO() 
     go 0 = return() 
     go n = go $! n-1 
    go (maxBound :: Int32) 
+2

P.S. el mismo gráfico en 'hp2pretty' http://i.imgur.com/P0F1N.png (la nueva herramienta de ezyang). –

+9

Correcto, aunque no estoy seguro de por qué Neil estaba viendo 8M en vivo. 1M es lo que esperaría con 7.0.3, porque el RTS libera espacio de pila excedente hasta que alcanza 1M. Tenga en cuenta que con 7.2.1 verá esa cifra bajar a 32k, porque la pila ahora se asigna en trozos de 32k. –

+2

(FYI: No escribí hp2pretty :-) –

1

Compilar el código con -O -ddump-simpl, veo la siguiente definición global en la salida simplificador:

lvl2_r12F :: [GHC.Types.Char] 
[GblId] 
lvl2_r12F = 
    GHC.Base.unpackAppendCString# "data C = C {a :: F {- " lvl1_r12D 

La entrada a la función de analizador se ha convertido en una constante de cadena global. Los Globales nunca son basura recolectada en GHC, entonces eso es probablemente lo que está ocupando los 8MB de memoria después de la recogida de basuras.

+1

CAF son basura recolectada: [ ver el comentario de GHC] (http://hackage.haskell.org/trac/ghc/wiki/Commentary/Rts/Storage/GC/CAFs?redirectedfrom=Commentary/Rts/Storage/CAFs). –

+2

Don's right - el CAF sería basura recolectada después de su último uso. –

+2

o /// o Y aparentemente eso se agregó a GHC hace más de diez años. Estoy corregido. – Heatsink

Cuestiones relacionadas