【发布时间】:2016-01-22 07:23:25
【问题描述】:
输入文件由两行组成,每行包含许多数字
1 2 3 4...
5 6 7 8...
我想处理每一行的数据,像这样:
doSomething :: [Int] -> [Int] -> Int
doSomething [] y = 0 -- stop execution. I'm concerted in memory behavior only.
doSomething (x:xs) y = doSomething xs y
main = do
inputdata <- getContents
let (x:xs) = lines inputdata
firstLine = map (read ::String->Int) $ words $ x
secondLine = map (read ::String->Int) $ words $ head xs
print $ doSomething firstLine secondLine
当我运行这个程序时,堆分析显示如下:
如果我不使用 secondLine (xs),那么这个程序会以恒定内存运行。
列表firstLine 的每个条目都会被 GC 处理然后丢弃。
为什么消耗的内存这么大? 我看到分配的内存量约为 100MB,但实际输入数据大小为 5MB。
head xs是否强制将整个第一行读入内存, 甚至secondLine根本没有使用?这是主要原因吗 堆分析图的内存增加?如何处理具有恒定内存的两行,例如后面的执行图?
如果 3) 的答案取决于处理顺序,如何先处理第二行,然后处理第一行?
【问题讨论】:
-
请注意,Haskell 的
String非常消耗内存,因为每个字符占用(至少)4 个字节。因此,如果您有一个 5MB 的文本文件,将其全部读入内存将使用 20+MB 的 RAM。然后,您将创建相同长度的Int列表,这至少应使内存增加一倍。如果它不懒惰,对doSomething的调用将再次翻倍,轻松达到 80+MB。使用ByteString可能会显着减少这种情况(并使读写效率更高)。 -
@cwyang 您的评论与上面的代码冲突。如果
doSomething强制y(secondLine) 被评估,这将导致lines继续直到找到换行符,同时记住它之前的内容,因为它将被放入firstLine。那么:正如您的评论所暗示的那样,您实际上是否在第一行之前访问了第二行? -
计算列表节点和内部元数据的开销,在 64 位架构
String上每个字符占用 24 个字节。如果您的输入本质上是 ASCII,那么空间使用量将增加 24 倍。String空间效率极低(而且通常效率低下)。因此Text和ByteString。 -
作为 Q3 在实践中的答案,您应该研究流式 IO 库。目前最流行的一些库是pipes、conduit 和io-streams。与尝试使用
getContents相比,其中任何一个都将更快、更不脆弱且更具功能性(例如支持压缩 IO)。 -
当使用
-O2编译时,您的程序实际上是使用恒定内存运行的。如果不使用firstLine,程序也会以恒定内存运行。
标签: performance haskell lazy-evaluation