2012-10-01 22 views
5

¿Cómo puedo leer múltiples archivos como una sola ByteString perezosamente con memoria constante? ¿Puedo leer n archivos de forma perezosa como una sola operación de E/S en Haskell?

readFiles :: [FilePath] -> IO ByteString 

actualmente tienen la siguiente implementación pero por lo que he visto de perfiles, así como mi entendimiento que va a terminar con n-1 de los archivos en la memoria.

readFiles = foldl1 joinIOStrings . map ByteString.readFile 
    where joinIOStrings ml mr = do 
           l <- ml 
           r <- mr 
           return $ l `ByteString.append` r 

entiendo que la falla aquí es que estoy aplicando las acciones IO continuación, reenvasado ellos para lo que creo que necesito es una forma de reemplazar el foldl1 joinIOStrings sin aplicarlos.

Respuesta

7

¿Cómo puedo leer múltiples archivos como una sola ByteString perezosamente con memoria constante?

Si desea un uso constante de la memoria, necesita Data.ByteString.Lazy. Un estricto ByteString no se puede leer perezosamente, y requeriría la memoria O(sum of filesizes).

Para un no demasiado gran número de archivos, la simple lectura de todos ellos (D.B.L.readFile lee muy lentamente) y la concatenación de los resultados es buena,

import qualified Data.ByteString.Lazy as L 

readFiles :: [FilePath] -> IO L.ByteString 
readFiles = fmap L.concat . mapM L.readFile 

El mapM L.readFile abrirá los archivos, pero sólo leer el contenido de cada uno archivo cuando se lo exija.

Si el número de archivos es grande, por lo que podría agotarse el límite de identificadores de archivos abiertos permitidos por el sistema operativo para un solo proceso, necesita algo más complicado. Puede cocinar su propia versión lenta de mapM,

import System.IO.Unsafe (unsafeInterleaveIO) 

mapM_lazy :: [IO a] -> IO [a] 
mapM_lazy [] = return [] 
mapM_lazy (x:xs) = do 
       r <- x 
       rs <- unsafeInterleaveIO (mapM_lazy xs) 
       return (r:rs) 

de modo que sólo se abrirá cada archivo cuando se necesitan sus contenidos, cuando los archivos leído anteriormente puede ya ser cerrado. Hay una pequeña posibilidad de que eso todavía se encuentre con los límites de recursos, ya que no está garantizado el momento de cerrar los identificadores.

O puede usar su favorito iteratee, enumerator, conduit o cualquier paquete que resuelva el problema de forma sistemática. Cada uno de ellos tiene sus propias ventajas y desventajas con respecto a los demás y, si está codificado correctamente, elimina la posibilidad de golpear accidentalmente el límite de recursos.

+0

Gracias, debería haber mencionado que ya estaba usando Data.ByteString.Lazy. Esto funcionó muy bien con el uso de memoria aumentando un poco con cada manejador abierto. También gracias por señalar los paquetes adicionales. Acabo de empezar a aprender Haskell y todavía no me he enterado. –

1

Supongo que está utilizando cadenas de bytes vagas (desde Data.ByteString.Lazy). Probablemente hay otras maneras de hacer esto, pero una opción es utilizar simplemente concat :: [ByteString] -> ByteString:

import Control.Monad 
import Data.ByteString.Lazy (ByteString) 
import qualified Data.ByteString.Lazy as ByteString 

readFiles :: [FilePath] -> IO ByteString 
readFiles = fmap ByteString.concat . mapM ByteString.readFile 

(Nota: no tengo tiempo para probar el código, pero la lectura de la documentación dice que esto debería funcionar)

Cuestiones relacionadas