对haskell中的大文件进行IO:性能问题

Joe*_*oel 6 io haskell lazy-evaluation bytestring

我正在尝试使用Haskell处理大文件.我希望逐字节地浏览输入文件,并在字节之后生成输出字节.当然,我需要使用合理大小的块(几KB)来缓冲IO.我不能这样做,我需要你的帮助.

import System 
import qualified Data.ByteString.Lazy as BL 
import Data.Word  
import Data.List

main :: IO () 
main =     
    do         
        args <- System.getArgs         
        let filename = head args         
        byteString <- BL.readFile filename         
        let wordsList = BL.unpack byteString         
        let foldFun acc word = doSomeStuff word : acc
        let wordsListCopy = foldl' foldFun [] wordsList
        let byteStringCopy = BL.pack (reverse wordsListCopy)
        BL.writeFile (filename ++ ".cpy") byteStringCopy
    where
        doSomeStuff = id
Run Code Online (Sandbox Code Playgroud)

我命名此文件TestCopy.hs,然后执行以下操作:

$ ls -l *MB
-rwxrwxrwx 1 root root 10000000 2011-03-24 13:11 10MB
-rwxrwxrwx 1 root root  5000000 2011-03-24 13:31 5MB
$ ghc --make -O TestCopy.hs 
[1 of 1] Compiling Main             ( TestCopy.hs, TestCopy.o )
Linking TestCopy ...
$ time ./TestCopy 5MB

real    0m5.631s
user    0m1.972s
sys 0m2.488s
$ diff 5MB 5MB.cpy
$ time ./TestCopy 10MB 

real    3m6.671s
user    0m3.404s
sys 1m21.649s
$ diff 10MB 10MB.cpy 
$ time ./TestCopy 10MB +RTS -K500M -RTS

real    2m50.261s
user    0m3.808s
sys 1m13.849s
$ diff 10MB 10MB.cpy 
$ 
Run Code Online (Sandbox Code Playgroud)

我的问题:5MB和10MB文件之间存在巨大差异.我希望表演在输入文件的大小上是线性的.请问我做错了什么,我怎样才能做到这一点?我不介意使用惰性字节串或其他任何东西,只要它有效,但它必须是标准的ghc库.

精确度:这是一个大学项目.而且我不是要复制文件.该doSomeStuff函数应执行我必须自定义的压缩/解压缩操作.

Lon*_*ong 8

对于分块输入处理,我会使用枚举器包.

import Data.Enumerator
import Data.Enumerator.Binary (enumFile)
Run Code Online (Sandbox Code Playgroud)

我们使用bytestrings

import Data.ByteString as BS
Run Code Online (Sandbox Code Playgroud)

和IO

import Control.Monad.Trans (liftIO)
import Control.Monad (mapM_)
import System (getArgs)
Run Code Online (Sandbox Code Playgroud)

您的主要功能可能如下所示:

main =
  do (filepath:_) <- getArgs
     let destination
     run_ $ enumFile filepath $$ writeFile (filepath ++ ".cpy")
Run Code Online (Sandbox Code Playgroud)

enumFile每个块读取4096个字节并将它们传递给writeFile,writeFile将其写下来.

enumWrite定义为:

enumWrite :: FilePath -> Iteratee BS.ByteString IO ()
enumWrite filepath =
  do liftIO (BS.writeFile filepath BS.empty)   -- ensure the destination is empty
     continue step
  where
  step (Chunks xs) =
    do liftIO (mapM_ (BS.appendFile filepath) xs)
       continue step
  step EOF         = yield () EOF
Run Code Online (Sandbox Code Playgroud)

如您所见,step函数采用块字节串并将它们附加到目标文件.这些块具有Stream BS.Bytestring类型,其中Stream定义为:

data Stream a = Chunks [a] | EOF
Run Code Online (Sandbox Code Playgroud)

在EOF步骤终止,屈服().

要对此进行更详细的阅读,我个人推荐Michael Snoymans 教程

数字

$ time ./TestCopy 5MB
./TestCopy 5MB  2,91s user 0,32s system 96% cpu 3,356 total

$ time ./TestCopy2 5MB
./TestCopy2 5MB  0,04s user 0,03s system 93% cpu 0,075 total
Run Code Online (Sandbox Code Playgroud)

这是一个很大的进步.现在,为了实现您的折叠,您可能想要编写一个Enumeratee,它用于转换输入流.幸运的是,枚举器包中已经定义了一个map函数,可以根据需要进行修改,即可以修改它以进行状态转换.

论中间结果的构建

您以相反的顺序构造wordsList并在之后反转它.我认为差异列表做得更好,因为附加只需要O(1)时间,因为追加只是一个函数组合.我不确定他们是否需要更多的空间.这是差异列表的草图:

type DList a = [a] -> [a]

emptyList :: DList a
emptyList = id

snoc :: DList a -> a -> DList a
snoc dlist a = dlist . (a:)

toList :: DList a -> [a]
toList dlist = dlist []
Run Code Online (Sandbox Code Playgroud)

可能不再需要这个答案,但我为了完整性而添加了它.


Chr*_*icz 1

我认为这是在 haskell 中读取大文件的后续从昨天。

尝试使用“-rtsopts -O2”而不是“-O”进行编译。

您声称“我想逐个字节地浏览输入文件,并生成一个又一个字节的输出”。但您的代码在尝试创建任何输出之前会读取整个输入。这并不能很好地代表目标。

在我的系统中,我看到“ghc -rtsopts --make -O2 b.hs”给出

(! 741)-> time ./b five
real 0m2.789s user 0m2.235s sys 0m0.518s
(! 742)-> time ./b ten
real 0m5.830s user 0m4.623s sys 0m1.027s
Run Code Online (Sandbox Code Playgroud)

现在对我来说看起来是线性的。