问候,
我试图理解为什么我看到整个文件使用以下程序加载到内存中,但如果你注释掉“(***)”下面的行,那么程序将以恒定的方式运行(约1.5M) ) 空间。
编辑:文件大约是660MB,第26列中的字段是类似'2009-10-01'的日期字符串,并且有一百万行。当它到达'getLine'时,该过程使用大约810MB
我是否正确地认为它与使用'split'拆分字符串有关,并且从文件中读取的底层ByteString不能被垃圾收集,因为它仍然被引用?但如果是这样,那么我认为BS.copy可以解决这个问题。任何想法如何强制计算 - 我似乎无法将'seq'放到正确的位置以产生效果。
(NB源文件是以制表符分隔的行)
提前致谢,
凯文
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
答案 0 :(得分:3)
您对last
的来电会强制列出jrs
。为了弄清楚这一点,它必须遍历整个文件,为jrs
中的每个条目构建thunks。因为你没有评估jrs
中的每个元素(除了最后一个),所以这些thunk会挂起来引用bytestring,所以它必须保留在内存中。
解决方案是强制评估那些thunk。因为我们在谈论空间,所以我做的第一件事实际上是以较小的格式存储您的信息:
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字节。
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
我们需要在从ByteString
转换为Record
时评估数据,因此我们使用parallel包并从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
同意,程序使用非常少的内存)。
对于其他误导性错误的答案感到抱歉 - 我迷上了这样一个事实,即增量处理修复了它并且没有真正意识到thunk真的在徘徊,不知道为什么我的大脑滑过那个。虽然我确实坚持要点,但你应该逐步处理这些信息,这使得所有这些答案都没有实际意义。
仅供参考,我发布的那些先前的堆配置文件中没有显示巨大的字节字符串,因为堆分析器不会跟踪外部分配(包括ByteString
)。
答案 1 :(得分:1)
这里似乎有两个问题:
我真的不知道该怎么说TomMD还没有说过的第一个;在loopInput
循环内部,jrs
永远不会被释放,因为它需要作为loopInput
的递归调用的参数。 (你知道当{***}存在时return ()
没有做任何事情,对吗?)
关于第二个问题,我认为你输入的ByteString没有被垃圾回收是正确的。原因是除了最后一个列表之外,你永远不会评估列表jrs
的元素,因此它们仍然包含对原始ByteString的引用(即使它们的格式为BS.copy ...
)。我认为用show $ last jrs
替换show jrs
会减少你的内存使用量;可以?或者,您可以尝试更严格的地图,例如
map' f [] = []
map' f (x:xs) = ((:) $! (f $! x)) (map' f xs)
将map
中的importRecords
替换为map'
,看看是否会降低内存使用量。