2

我尝试在 haskell 中解析大型日志文件。我正在使用System.IO.Streams,但是当我折叠输入时,它似乎占用了很多内存。这是两个(丑陋的)示例:

首先将 1M 加载Int到列表中的内存中。

let l = foldl (\aux p -> p:aux) [] [1..1000000]
return (sum l)

内存消耗很漂亮。Ints 吃 3Mb,而列表需要 6Mb:

查看 1M Int 的构建列表的内存消耗

然后对 ByteStrings 流进行同样的尝试。我们需要一个丑陋的来回对话,但我认为没有任何区别

let s = Streams.fromList $ map (B.pack . show) [1..1000000]

l <-  s >>= 
      Streams.map bsToInt  >>=
      Streams.fold (\aux p -> p:aux) [] 

return (sum l)

查看从流中构建 Int 列表的内存消耗

为什么需要更多内存?如果我从文件中读取它,情况会更糟。它需要 90Mb

result <- withFileAsInput file load
putStrLn $ "loaded " ++ show result
where load is = do
        l <- Streams.lines is >>= 
             Streams.map bsToInt  >>=
             Streams.fold (\aux p -> p:aux) [] 

        return (sum l)

我的假设是 Streams.fold 有一些问题。因为库的内置 countInput 方法不使用它。任何想法?

编辑

经过调查,我将问题简化为:为什么这段代码需要额外的 50Mb?

do
  let l = map (Builder.toLazyByteString . intDec ) [1..1000000]
  let l2 = map (fst . fromJust . B.readInt) l
  return (foldl' (\aux p -> p:aux) [] l2)

没有转换它只需要 30Mb,转换 90Mb。

4

2 回答 2

3

在您的第一个示例中, thefoldl (\aux p -> p:aux) []是多余的。它构造了一个与它作为参数的列表具有相同元素的列表!没有冗余,该示例等效于sum [1..1000000]or foldl (+) 0 [1..1000000]。此外,最好使用严格的左折叠foldl'来避免堆上可归约表达式的积累。请参阅Haskell wiki 上的Foldr Foldl Foldl' 。

在您的最后一个示例中,您System.IO.Streams.Combinators.fold用于构建从文件中读取的所有整数的列表,然后尝试像您在第一个示例中所做的那样对列表求和。

问题在于,由于IOmonad强加的文件读取操作的顺序,文件中的所有数据在您开始对列表求和之前都已被读取,并且潜伏在堆上,可能仍未从原始字符串转换并取更多记忆。

解决方案是在每个新元素到达时在折叠执行实际求和;这样你就不需要在任何时候在内存中拥有完整的列表,只有当前元素(能够在执行 I/O 时做到这一点是流库的目标之一)。并且提供的折叠io-streams是严格的,类似于foldl'. 因此,您也不会在堆上累积可简化的表达式。

尝试类似的东西System.IO.Streams.Combinators.fold (+) 0

于 2013-08-20T19:07:07.643 回答
-1

所以问题是ByteStrings 的懒惰创建而不是迭代器。请参阅 为什么在 Haskell 中创建和处理临时 ByteStrings 会占用我的内存?

于 2013-08-25T22:36:50.567 回答