【问题标题】:Laziness in folding the result of a map operation折叠地图操作结果的懒惰
【发布时间】:2018-12-08 23:18:48
【问题描述】:

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

编辑:一个更简单的例子

示例(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 的整个结果都存储在内存中

我期待 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)

【问题讨论】:

  • convertByteStringToBreductBsToC 是什么?原因可能就在那里。
  • 感谢您提供最小示例!您是否也可以将其重写为完整且可验证?见MCVE

标签: haskell lazy-evaluation


【解决方案1】:

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

【讨论】:

  • 实际上是将数据组合到图中的节点中。最后 Gr 图中只有 7 个节点,所以我认为这不是源。节点永远不会被删除,因此图形的最大大小为 7 个节点。
  • 经过更多的玩弄。看起来 Graph 肯定是这里的一个因素。但问题不在于图表的大小。实现中的其他内容正在阻止列表项被释放。当我有更多时,我会更新。
  • @MikeLui 我们确定图表没有收集 thunk 吗?您正在使用foldl',这会使图形变得严格,但不一定会使图形内部的数据变得严格。它只是确保对弱头范式进行评估。
  • @DarthFennec 绝对是这样。我投入了一堆爆炸模式,看到常驻内存从 4GB 下降到 10MB。将缩小范围并更新。
【解决方案2】:

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

我的 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 杂注,并在稍后在 foldl' 累积函数中对我的数据进行模式匹配时使用它们(参考原始问题):

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-11-22
    • 2017-04-30
    • 1970-01-01
    • 2021-01-27
    • 2019-04-07
    • 2011-12-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多