懒惰地折叠地图操作的结果
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
的整个结果都存储在内存中
我原以为 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
操作的整个结果将在内存中。
编辑
convertByteStringToC
和 reduceBsToC
被要求澄清:
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 添加到在我的解析器中得到 return
ed 的数据构造函数。例如:
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 是在模式匹配之前构建的,当它在折叠中使用时。
为什么这个函数会导致内存占用过高,有什么减少内存占用的建议吗?
编辑:一个更简单的例子
示例(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
操作的整个结果将在内存中。
编辑
convertByteStringToC
和 reduceBsToC
被要求澄清:
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 添加到在我的解析器中得到 return
ed 的数据构造函数。例如:
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 是在模式匹配之前构建的,当它在折叠中使用时。