2012-10-01 2 views
5

Wie kann ich mehrere Dateien als einzelnen ByteString träge mit konstantem Speicher lesen? Kann ich n Dateien in Haskell als einzelne IO-Operation lazily lesen?

readFiles :: [FilePath] -> IO ByteString 

Im Moment habe ich die folgende Implementierung, sondern von dem, was ich aus Profilierungs gesehen habe und mein Verständnis werde ich mit n-1 der Dateien im Speicher beenden.

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

Ich verstehe, dass der Fehler hier ist, dass ich die IO Aktionen beantrage sie dann so Umpackzentrums, was ich glaube, ich brauche eine Möglichkeit, die foldl1 joinIOStrings ohne ihre Anwendung zu ersetzen.

Antwort

7

Wie kann ich mehrere Dateien als einzelnen ByteString träge mit konstantem Speicher lesen?

Wenn Sie eine konstante Speichernutzung wünschen, benötigen Sie Data.ByteString.Lazy. Ein strenger ByteString kann nicht träge gelesen werden und würde O(sum of filesizes) Speicher benötigen.

Für eine nicht zu große Anzahl von Dateien, einfach zu lesen, sie alle (D.B.L.readFile liest träge) und die Ergebnisse verketten ist gut,

import qualified Data.ByteString.Lazy as L 

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

Die mapM L.readFile die Dateien öffnen, aber nur den Inhalt von jedem gelesen Datei, wenn es angefordert wird.

Wenn die Anzahl der Dateien groß ist, so dass die Grenze der offenen Dateizugriffsnummern, die vom Betriebssystem für einen einzelnen Prozess zulässig sind, erschöpft sein kann, benötigen Sie etwas komplizierteres. Sie können Ihre eigene faul Version von 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) 

so dass jede Datei nur dann geöffnet werden, wenn sein Inhalt benötigt werden, wenn zuvor Dateien lesen kann bereits geschlossen kochen. Es gibt eine geringe Möglichkeit, dass das immer noch in Ressourcengrenzen läuft, da die Zeit des Schließens der Handles nicht garantiert ist.

Oder Sie können Ihre bevorzugten iteratee, enumerator, conduit oder was auch immer Paket, das das Problem auf systematische Weise löst. Jeder von ihnen hat seine eigenen Vor- und Nachteile in Bezug auf die anderen und eliminiert bei korrekter Codierung die Möglichkeit, dass er versehentlich das Ressourcenlimit erreicht.

+0

Danke, ich hätte erwähnen sollen, dass ich Data.ByteString.Lazy bereits verwendet habe. Dies funktionierte gut mit der Speichernutzung, die mit jedem offenen Griff etwas anstieg. Auch danke, dass ich auf die zusätzlichen Pakete hingewiesen habe. Ich habe gerade begonnen, Haskell zu lernen und bin noch nicht auf sie gestoßen. –

1

Ich nehme an, dass Sie Lazy-Byte-Strings (von Data.ByteString.Lazy) verwenden. Es gibt wahrscheinlich auch andere Möglichkeiten, dies zu tun, aber eine Möglichkeit ist, einfach zu verwenden 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 

(Anmerkung: Ich habe keine Zeit, um den Code zu testen, aber die Dokumentation zu lesen sage, dass dies funktionieren soll)