2012-07-25 12 views
6

En GHCi, corro esta sencilla prueba:¿Por qué el codificadorFile de Data.Binary no actúa como flojo?

encodeFile "test" [0..10000000] 

La línea funciona muy rápido (< 10seg), pero mi uso de la memoria se dispara hasta ~ 500 MB antes de que termine. ¿No debería encodeFile ser flojo ya que usa ByteString.Lazy?


Editar: La respuesta de Roman a continuación es genial! También quiero señalar this answer a otra pregunta, eso explica por qué Data.Binary hace una codificación estricta en las listas y proporciona un trabajo un poco más elegante.

+0

En general, GHCi no es una buena opción para ningún tipo de perfil. No realiza ningún tipo de optimización e interpreta el código Haskell, por lo que el uso de la memoria y las características de rendimiento a menudo son completamente diferentes en comparación con el compilado Haskell. Debe compilar con 'ghc -O2' y ver si el problema se repite allí. – shang

+0

Encontré el problema en un programa compilado con ghc -O2, y trabajé en esto. –

Respuesta

9

Así es como se define serialización de listas:

instance Binary a => Binary [a] where 
    put l = put (length l) >> mapM_ put l 

Es decir, serializar en primer lugar la longitud de la lista, a continuación, serializar la propia lista.

Para conocer la longitud de la lista, debemos evaluar toda la lista. Pero no podemos recoger basura, porque sus elementos son necesarios para la segunda parte , mapM_ put l. Por lo tanto, toda la lista debe almacenarse en la memoria después de evaluar la longitud y antes de que comience la serialización de los elementos.

Así es como el perfil del montón se parece a:

profile

Aviso cómo crece mientras que la lista está siendo construido para calcular su longitud, y luego disminuye mientras que los elementos están en serie y pueden ser recogidos por el GC.

Entonces, ¿cómo solucionarlo? En tu ejemplo, ya sabes la longitud. Por lo que puede escribir una función que toma la longitud conocida, a diferencia de la computación que:

import Data.Binary 
import Data.ByteString.Lazy as L 
import qualified Data.ByteString as B 
import Data.Binary.Put 

main = do 
    let len = 10000001 :: Int 
     bs = encodeWithLength len [0..len-1] 
    L.writeFile "test" bs 

putWithLength :: Binary a => Int -> [a] -> Put 
putWithLength len list = 
    put len >> mapM_ put list 

encodeWithLength :: Binary a => Int -> [a] -> ByteString 
encodeWithLength len list = runPut $ putWithLength len list 

Este programa se ejecuta dentro de 53k de espacio de almacenamiento dinámico.

También puede incluir una función de seguridad en putWithLength: calcule la longitud mientras serializa la lista, y verifique con el primer argumento al final. Si hay una discrepancia, arroje un error.

Ejercicio: ¿por qué aún necesita pasar la longitud a putWithLength en lugar de usar el valor calculado como se describe arriba?

+0

+1 para mostrar el perfil del montón (y ser correcto, por supuesto) – alternative

+0

Respuesta al ejercicio: porque el formato requiere que la longitud sea lo primero en la codificación, pero no tendrá la longitud calculada hasta el final. –

Cuestiones relacionadas