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 と 10MB のファイルには大きな違いがあります。パフォーマンスが入力ファイルのサイズに比例するようにしたいと思います。私が間違っていることを教えてください。どうすればこれを達成できますか? 動作する限り、遅延バイト文字列などを使用してもかまいませんが、標準の ghc ライブラリである必要があります。

精度: 大学のプロジェクト用です。そして、私はファイルをコピーしようとしていません。関数は、doSomeStuffカスタマイズする必要がある圧縮/解凍アクションを実行します。

4

2 に答える 2

9

チャンク入力処理には、列挙子パッケージを使用します。

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関数はバイト文字列のチャンクを取得し、それらを宛先ファイルに追加します。これらのチャンクのタイプはStreamBS.Bytestringです。ここで、Streamは次のように定義されます。

data Stream a = Chunks [a] | EOF

EOFステップで終了すると、()が生成されます。

これについてもっと詳しく読むために、私は個人的にMichaelSnoymansチュートリアルをお勧めします

数字

$ 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を作成することをお勧めします。幸い、列挙子パッケージにはすでにマップ関数が定義されており、必要に応じて変更できます。つまり、状態を引き継ぐように変更できます。

中間結果の構築について

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 で大きなファイルを読むの続きだと思いますか? 昨日から。

「-O」だけでなく、「-rtsopts -O2」でコンパイルしてみてください。

「入力ファイルをバイトごとに参照し、バイトごとに出力バイトを生成したい」と主張します。ただし、コードは出力を作成する前に入力全体を読み取ります。これは、目標をあまり代表していません。

私のシステムでは、「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 に答える