我有个工作用的小工具(移动端应用),读取一份分析导出文件,是一个会话事件的 CSV,大约 600 万行,差不多 300MB,输出平均会话时长。结果是对的。但是跑完整文件时,我那台老 Intel MacBook 的风扇狂转,+RTS -s 显示最大驻留内存远超 1 GB。
我一开始用的是 foldl,后来换成 foldl',峰值内存几乎没变。cabal 文件里本来就有 -O2,也没什么实际差别。
meanMillis :: [Session] -> Double
meanMillis ss
| n == 0 = 0
| otherwise = fromIntegral total / fromIntegral n
where
(total, n) = foldl' step (0 :: Int, 0 :: Int) ss
step (t, c) s = (t + sessMillis s, c + 1)
main 里是这样写的。
contents <- readFile (optPath opts)
let sessions = mapMaybe parseLine (drop 1 (lines contents))
Session 就是个普通 record,里面一个 String 和一个 Int,按屏幕过滤用的就是 filter。
我首先怀疑 readFile 和 lines,所以想过改成 hGetLine 循环,但还没试。如果文件是惰性读取的,每行解析完就丢掉,我不明白为什么严格 fold 还不够。这台机器上带 profiling 的构建特别慢,所以我基本是凭 -s 的数字在猜。
内存到底花到哪里去了?
从英语翻译·查看原文I've got a small tool for work (mobile app) that reads an analytics export, a CSV of session events, about 6 million lines and roughly 300MB, and prints the mean session length. It gives the right answer. But on the full file the fans on my old Intel MacBook spin up, and +RTS -s reports maximum residency of well over a gigabyte.
I wrote it with foldl first, then swapped in foldl', and peak memory barely moved. -O2 is already in the cabal file and made no real difference either.
meanMillis :: [Session] -> Double
meanMillis ss
| n == 0 = 0
| otherwise = fromIntegral total / fromIntegral n
where
(total, n) = foldl' step (0 :: Int, 0 :: Int) ss
step (t, c) s = (t + sessMillis s, c + 1)
and in main:
contents <- readFile (optPath opts)
let sessions = mapMaybe parseLine (drop 1 (lines contents))
Session is a plain record with a String and an Int, and the filter by screen is just filter.
My first suspect is readFile and lines, so I thought about an hGetLine loop, but I haven't tried it. I don't see why the strict fold wouldn't be enough if the file is consumed lazily and each line is dropped after parsing. A profiling build is very slow on this machine, so I'm mostly guessing from the -s numbers.
Where is the memory actually going?