5

ご挨拶、

次のプログラムでファイル全体がメモリにロードされている理由を理解しようとしていますが、「(***)」の下の行をコメントアウトすると、プログラムは一定の(約1.5M)スペースで実行されます。

編集:ファイルは約660MBで、列26のフィールドは「2009-10-01」のような日付文字列であり、100万行あります。このプロセスは、「getLine」に到達するまでに約810MBを使用します。

'split'を使用した文字列の分割に関連していると思いますか?ファイルから読み取られた基になるByteStringは、まだ参照されているため、ガベージコレクションできませんか?しかし、もしそうなら、私はBS.copyがそれを回避するだろうと思いました。計算を強制する方法についてのアイデア-効果を発揮するために「seq」を適切な場所に配置できないようです。

(注:ソースファイルはタブ区切りの行です)

前もって感謝します、

ケビン

module Main where

import System.IO
import qualified Data.ByteString.Lazy.Char8 as BS
import Control.Monad


type Record = BS.ByteString

importRecords :: String -> IO [Record]
importRecords filename = do
    liftM (map importRecord.BS.lines) (BS.readFile filename)

importRecord :: BS.ByteString -> Record
importRecord txt = r
  where 
    r = getField 26
    getField f = BS.copy $ ((BS.split '\t' txt) !! f)

loopInput :: [Record] -> IO ()
loopInput jrs = do
    putStrLn $ "Done" ++ (show $ last jrs)
    hFlush stdout
    x <- getLine
    return ()

    -- (***)
    loopInput jrs

main = do 
    jrs <- importRecords "c:\\downloads\\lcg1m.txt"
    loopInput jrs
4

2 に答える 2

3

lastリストを強制するための呼び出し、 jrs。それを理解するには、ファイル全体を実行して、の各エントリのサンクを構築する必要がありますjrs。(最後の要素を除いて)の各要素を評価していないため、jrsこれらのサンクはバイト文字列への参照でハングアウトするため、メモリ内にとどまる必要があります。

解決策は、それらのサンクの評価を強制することです。スペースについて話しているので、私が最初にしたことは、実際には情報をより小さな形式で保存することでした。

type Year   = Word16
type Month  = Word8
type Day    = Word8
data Record = Rec {-# UNPACK #-} !Year {-# UNPACK #-} !Month {-# UNPACK #-} !Day 
        deriving (Eq, Ord, Show, Read)

これにより、その醜い10バイトのバイト文字列(+構造情報の約16バイトのオーバーヘッド)が約8バイトに削減されます。

importRecordtoRecord r正しいタイプを取得するには、を呼び出す必要があります。

toRecord :: BS.ByteString -> Record
toRecord bs =
    case BS.splitWith (== '-') bs of
            (y:m:d:[]) -> Rec (rup y) (rup m) (rup d)
            _ -> Rec 0 0 0

rup :: (Read a) => BS.ByteString -> a
rup = read . BS.unpack

ByteStringからに変換するときにデータを評価する必要があるRecordので、並列パッケージを使用して、DeepSeqからNFDataインスタンスを定義しましょう

instance NFData Record where
    rnf (Rec y m d) = y `seq` m `seq` d `seq` ()

これで準備が整いました。mainを使用するように変更しましたevalList。これにより、最後の関数が必要な関数の前にリスト全体が強制されます。

main = do
    jrs <- importRecords "./tabLines"
    let jrs' = using jrs (evalList rdeepseq)
    loopInput jrs'

そして、ヒーププロファイルが美しく見えることがわかります(そしてtop、プログラムが使用するメモリはごくわずかです)。

代替テキスト

他の誤解を招く間違った答えについて申し訳ありません-インクリメンタル処理がそれを修正し、サンクが本当にぶらぶらしていることに本当に気づかなかったという事実に夢中になりました、なぜ私の脳がそれを滑ったのかわかりません。私は要点を支持しますが、この情報を段階的に処理して、この答えをすべて無意味にする必要があります。

参考までに、私が投稿した以前のヒーププロファイルには、巨大なバイト文字列は表示されませんでした。これは、外部割り当て(を含むByteString)がヒーププロファイラーによって追跡されないためです。

于 2010-10-19T00:17:05.390 に答える
1

ここには2つの質問があるようです。

  • なぜメモリ使用量は行の有無に依存するのですか(***);
  • (***)のメモリ使用量が、たとえば40MBではなく約800MBであるのはなぜですか。

TomMDがまだ言っていない最初のものについて何を言うべきか本当にわかりません。loopInputループ内でjrsは、の再帰呼び出しの引数として必要になるため、解放することはできませんloopInput。(return ()(***)が存在する場合、それは何もしませんよね?)

2番目の質問については、入力ByteStringがガベージコレクションされていないのは正しいと思います。その理由は、リストの要素をjrs最後の要素以外に評価することは決してないため、元のByteStringへの参照が含まれているためです(形式はBS.copy ...)。に置き換えるshow $ last jrsshow jrsメモリ使用量が減ると思います。しますか?または、次のようなより厳密なマップを試すこともできます

map' f []     = []
map' f (x:xs) = ((:) $! (f $! x)) (map' f xs)

mapinimportRecordsをに置き換えてmap'、メモリ使用量が削減されるかどうかを確認します。

于 2010-10-20T04:32:23.123 に答える