使用Data.Map计算不同的值会泄漏内存

时间:2012-12-24 19:01:38

标签: haskell memory-leaks map io

以下程序在计算250 MB文件中的不同行长度时使用100+ MB RAM。如何修复它以减少使用RAM?我想我错误地使用了惰性IO,foldrData.Map的懒惰值。

import Control.Applicative
import qualified Data.Map as M
import Data.List

main = do
  content <- readFile "output.csv"
  print $ (foldr count M.empty . map length . lines) content

count a b = M.insertWith (+) a 1 b

2 个答案:

答案 0 :(得分:5)

中的第一个大错误
main = do
  content <- readFile "output.csv"
  print $ (foldr count M.empty . map length . lines) content

count a b = M.insertWith (+) a 1 b

正在使用foldr。这构造了表单

的表达式
length firstLine `count` length secondLine `count` ... `count` length lastLine `count` M.empty

遍历构成thunk的整个行列表 - 此时甚至不会因为懒惰而评估length调用 - 然后从右到左进行评估。因此,除了用于构建Map

的thunk之外,整个文件内容都在内存中

如果你从一系列事物中建立一个地图,总是使用一个严格的左折(好吧,如果列表很短,而且事情不是很大,那没关系)除非语义需要一个正确的折叠(如果您使用非交换函数组合值,可能就是这种情况,但即使这样,在构建映射之前,通常最好使用左侧折叠和reverse列表)。

Data.Map s(或Data.IntMap s)是严格的,只有这样才能在遍历整个列表之前生成部分输出,因此foldr的强度不能在这里使用。

下一个(可能的)问题是(再次懒惰)当你将它们放入Map时你没有评估映射到的值,所以如果有一个特别经常出现的行长度,那么这个值成为一个巨大的蠢货

((...((1+1)+1)...+1)+1)

成功

main = do
  content <- readFile "output.csv"
  print $ (foldl' count M.empty . map length . lines) content

count mp a = M.insertWith' (+) a 1 mp

这样一旦读入行就可以对行进行垃圾收集,并且不会在值中积累任何行。这样你就不会一次在内存中需要多行文件,甚至不需要完全在内存中,因为lengthMap中被记录之前会被评估。

如果您的containers套餐足够新,您也可以

import Data.Map.Strict

并使用count离开insertWith(没有素数,Data.Map.Strict模块始终评估放入地图的值。)

答案 1 :(得分:0)

降低最大停留时间的一种方法是使用IntMap而不是Map,这是Map密钥的Int数据结构的专用版本。这是一个简单的改变:

import Control.Applicative
import qualified Data.IntMap as I
import Data.List

main = do
  content <- readFile "output.csv"
  print $ (foldr count I.empty . map length . lines) content

count a b = I.insertWith (+) a 1 b

使用/usr/share/dict/words将此版本与您的版本进行比较,发现最大驻留时间从大约100MB到60MB。请注意,这也没有任何优化标志。如果你发动这些,最大居住率很可能会进一步改善。