懒惰地折叠地图操作的结果

Laziness in folding the result of a map operation

为什么这个函数会导致内存占用过高,有什么减少内存占用的建议吗?

编辑:一个更简单的例子

示例(1) GC 发现打印后每个元素都不需要,因为使用的内存很少:

printThings = readThing >=> mapM_ (parseThing >>> print)

示例(2)整个列表保存在内存中

printThings = readThing >=> map parseThing >>> print

请注意在我下面的确切问题中,我折叠了地图结果,希望只评估每个元素,然后让 GC 释放该元素。


我有一个程序可以读取数据、解析数据并减少数据。作为 最小 示例:

aFoo :: FilePath -> IO ()
aFoo = readFile >=> lines >>> map convertStringToB >>> reduceBsToC >>> print
reduceBsToC = foldl' bToC base

更具体地说,我懒洋洋地阅读文件:

import Data.ByteString.Lazy.Char8 as B
actualFoo = B.readFile >=> B.split '\n' >>> map convertByteStringToB >>> reduceBsToC >>> print)

我看到此程序占用大量内存(我的输入约为 4GB)似乎是:

我原以为 map convertByteStringStringToB 创建的 [B] 会被懒惰地折叠阅读。如果我只是打印 [B] 我看不到这种行为,并且使用的内存要少得多(~10MB):

readFoo :: FilePath -> IO [ByteString]
readFoo = B.readFile >=> B.split '\n' >>> return
printFoo :: FilePath -> IO ()
printFoo = readFoo >=> mapM_ (convertByteStringToB >>> print)
-- Lazily reading in file and converting each 'line'

我知道 foldl' 的实现是:

foldl' f z []     = z
foldl' f z (x:xs) = let z' = z `f` x 
                    in seq z' $ foldl' f z' xs

我假设(x:xs)使用thunk来表示xs,否则map操作的整个结果将在内存中。


编辑

convertByteStringToCreduceBsToC 被要求澄清:

convertByteStringToC 是一个 Megaparsec 函数,对于这种格式来说太长了。

reduceBsToC 使用 fgl。 (简化):

type MyGraph = Gr UNode UEdge
reduceBsToC :: MyGraph -> B -> MyGraph
reduceBsToC gr End = gr
reduceBsToC gr b = maybe makeDefault setGraph (tryAddToGr gr b)

reduceBsToC 正在生成 Gr 图表。它表示为 Map,这不是惰性或流式结构(它是一棵树)。因此,折叠正在累积一个可能与原始列表一样大的图形。

除非添加完整且可验证的示例,否则我能够找到问题所在。

我的 Megaparsec 计算在最后 print 的最后进行了延迟评估,这意味着整个文件被读入以生成解析计算,但没有立即执行。

我将 strict fields 添加到在我的解析器中得到 returned 的数据构造函数。例如:

data MyParsedData = MyParsedData { value1 :: !Int, value2 :: !Int }

这会强制以下内容在构建 MyParsedData 时立即解析,而不是延迟解析。

myParse = do
    val1 <- parseVal1
    val2 <- parseVal2
    return $ MyParsedData val1 val2

此外,我尝试放弃严格的字段,而是使用 BangPatterns,这也更正了问题。这涉及添加 BangPatterns pragma,并在稍后在 foldl' 累积函数(参考原始问题)中对我的数据进行模式匹配时使用它们:

tryAddtoGr gr (MyParsedData !val1 !val2) = ...

这会强制在折叠期间执行解析。

澄清:MyParsedData 是在模式匹配之前构建的,当它在折叠中使用时。