7

我正在尝试使用 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

我将此文件命名为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 
$ 

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

Precision:这是一个大学项目。而且我不是要复制文件。该doSomeStuff功能应执行我必须自定义的压缩/解压缩操作。

4

2 回答 2

9

对于分块输入处理,我会使用enumerator包。

import Data.Enumerator
import Data.Enumerator.Binary (enumFile)

我们使用字节串

import Data.ByteString as BS

和 IO

import Control.Monad.Trans (liftIO)
import Control.Monad (mapM_)
import System (getArgs)

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

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

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

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

data Stream a = Chunks [a] | EOF

在 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

这是一个很大的进步。现在,为了实现您的折叠,您可能需要编写一个 Enumeratee,它用于转换输入流。幸运的是,在 enumerator 包中已经定义了一个 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 []

可能不再需要这个答案,但为了完整起见,我添加了它。

于 2011-03-27T13:44:19.280 回答
2

我认为这是在 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

现在在我看来是线性的。

于 2011-03-24T15:09:43.470 回答