Haskell ByteStrings - 最终将大文件加载到内存中

Kev*_*vin 5 haskell lazy-evaluation bytestring

问候,

我试图理解为什么我看到整个文件使用以下程序加载到内存中,但如果你注释掉"(***)"下面的行,那么程序将在恒定(约1.5M)空间中运行.

编辑:文件大约是660MB,第26列中的字段是一个类似'2009-10-01'的日期字符串,并且有一百万行.当它到达'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
Run Code Online (Sandbox Code Playgroud)

Tho*_*son 3

您对last强制列表的调用,jrs。为了弄清楚这一点,它必须遍历整个文件,为jrs. 因为您没有评估其中的每个元素jrs(最后一个除外),所以这些 thunk 会与字节串的引用一起出现,因此必须保留在内存中。

解决方案是强制对这些重击进行评估。因为我们谈论的是空间,所以我做的第一件事实际上是以较小的格式存储您的信息:

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

这将丑陋的 10 字节字节串(+ 约 16 字节结构信息的开销)减少到大约 8 字节。

importRecord现在必须调用toRecord 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
Run Code Online (Sandbox Code Playgroud)

ByteString当我们从 转换为时,我们需要评估数据Record,因此让我们使用并行包并从DeepSeq定义一个 NFData 实例。

instance NFData Record where
    rnf (Rec y m d) = y `seq` m `seq` d `seq` ()
Run Code Online (Sandbox Code Playgroud)

现在我们准备好了,我将 main 修改为 use evalList,从而将整个列表强制放在需要最后一个列表的函数之前:

main = do
    jrs <- importRecords "./tabLines"
    let jrs' = using jrs (evalList rdeepseq)
    loopInput jrs'
Run Code Online (Sandbox Code Playgroud)

我们可以看到堆配置文件看起来很漂亮(并且top同意,该程序使用很少的内存)。

替代文本

对于其他误导性的错误答案感到抱歉 - 我着迷于增量处理修复它的事实,并且没有真正意识到重击声确实存在,不知道为什么我的大脑会忽略这一点。尽管我确实坚持要点,但您应该逐步处理这些信息,从而使所有这些答案变得毫无意义。

仅供参考,巨大的字节串没有出现在我之前发布的堆配置文件中,因为ByteString堆分析器不跟踪外部分配(包括 )。