将[(K,[V])]转换为[(V,[K])时的内存故障]

时间:2012-12-06 01:41:04

标签: memory haskell parsec

我有一个279MB的文件,其中包含大约1000万个键/值对,大约有500,000个唯一键。它按键分组(每个键只需要写一次),因此给定键的所有值都在一起。

我想要做的是转换关联,创建一个文件,其中对按值分组,并且给定值的所有键都存储在一起。

目前,我正在使用Parsec作为元组列表(K,[V])读取(使用惰性IO,因此我可以将其作为流处理,而Parsec正在处理输入文件),其中:

newtype K = K Text deriving (Show, Eq, Ord, Hashable)
newtype V = V Text deriving (Show, Eq, Ord, Hashable)

tupleParser :: Parser (K,[V])
tupleParser = ...

data ErrList e a = Cons a (ErrList e a) | End | Err e                

parseAllFromFile :: Parser a -> FilePath-> IO (ErrList ParseError a) 
parseAllFromFile parser inputFile = do                               
  contents <- readFile inputFile                                     
  let Right initialState = parse getParserState inputFile contents   
  return $ loop initialState                                         
  where loop state = case unconsume $ runParsecT parser' state of    
                        Error err             -> Err err             
                        Ok Nothing _ _        -> End                 
                        Ok (Just a) state' _  -> a `Cons` loop state'
        unconsume v = runIdentity $ case runIdentity v of            
                                      Consumed ma -> ma              
                                      Empty ma -> ma                 
        parser' = (Just <$> parser) <|> (const Nothing <$> eof)      

我尝试将元组插入Data.HashMap.Map V [K]以转置关联:

transpose :: ErrList ParseError (K,[V]) -> Either ParseError [(V,[K])]          
transpose = transpose' M.empty                                                   
  where transpose' _ (Err e)          = Left e                                
        transpose' m End              = Right $ assocs m                      
        transpose' m (Cons (k,vs) xs) = transpose' (L.foldl' (include k) m vs) xs
        include k m v = M.insertWith (const (k:)) v [k] m                  

但是当我尝试它时,我收到了错误:

memory allocation failed (requested 2097152 bytes)

我能想到一些我做错的事情:

  1. 2MB似乎有点低(远低于我机器安装的2GB RAM),所以也许我需要告诉GHC可以使用更多?
  2. 我的问题可能与算法/数据结构有关。也许我正在使用错误的工具来完成工作?
  3. 我尝试使用懒惰的IO可能会回来咬我。
  4. 我现在倾向于(1),但我无论如何都不确定。

2 个答案:

答案 0 :(得分:1)

数据是否有可能增加?如果是,那么我建议不要将while文件读入内存并以另一种方式处理数据。

一个简单的可能性就是使用关系数据库。这非常简单 - 只需加载数据,创建适当的索引并根据需要对其进行排序。数据库将为您完成所有工作。我肯定会推荐这个。


另一种选择是创建自己的基于文件的机制。例如:

  1. 选择一些合理加载到内存中的限制l
  2. 创建n = d `div` l个文件,其中d是您的数据总量。 (希望这不会超过您的文件描述符限制。您也可以在每次操作后关闭并重新打开文件,但这会使该过程非常慢。)
  3. 按顺序处理输入,并将每对(k,v)放入文件编号hash v `mod` l。这可确保具有相同值v的对将最终位于同一文件中。
  4. 单独处理每个文件。
  5. 将它们合并在一起。
  6. 它本质上是一个带文件桶的哈希表。此解决方案假设每个值具有大致相同数量的键(否则某些文件可能会异常大)。


    您还可以实现external sort,这样您就可以对任何数量的数据进行排序。

答案 1 :(得分:-1)

要允许大于可用内存的文件,最好一次以一口大小的方式处理它们。

这是将文件A复制到新文件B的实体算法:

  • 创建文件B并将其锁定到您的计算机
  • 开始循环
    • 如果文件A中没有下一行,则退出循环
    • 读入文件A的下一行
    • 将处理应用于该行
    • 检查文件B是否包含已经
    • 的行
    • 如果文件B不包含该行,则将该行附加到文件B
    • 转到循环开始
  • 解锁文件B

将文件A复制到临时文件夹并在使用它时锁定它也是值得的,这样就不会阻止网络上的其他人更改原始文件,但是你有文件的快照正如程序开始时那样。

我打算将来重新回答这个问题并添加代码。