haskell io stream memory



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

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

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

内存消耗是美丽的。Ints吃了3Mb,列表需要6Mb:

参见1M Int 的构建列表的内存消耗

然后对Stream of 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。

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

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

问题是,由于IO monad强制执行文件读取操作的顺序,在开始对列表求和之前,文件中的所有数据都已被读取,并且潜伏在堆中,可能仍然未从原始字符串转换,甚至占用更多的内存。

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

试试System.IO.Streams.Combinators.fold (+) 0之类的东西。

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

最新更新