使用延迟文本和字节索引处理非常大的文本文件

时间:2013-11-04 23:54:13

标签: haskell text hashmap bigdata file-processing

我正在尝试处理一个非常大的unicode文本文件(6GB +)。我想要的是计算每个独特单词的频率。当我遍历文件时,我使用严格的Data.Map来跟踪每个单词的计数。 这个过程需要太多时间和太多内存(20GB +)。我怀疑地图很大,但我不确定它应该达到文件大小的5倍! 代码如下所示。请注意,我尝试了以下内容:

  • 使用Data.HashMap.Strict代替Data.Map.StrictData.Map似乎在较慢的内存消耗增长率方面表现更好。

  • 使用惰性ByteString而非懒惰Text读取文件。然后我将其编码为Text进行一些处理,然后将其编码回ByteString IO

    import Data.Text.Lazy (Text(..), cons, pack, append)
    import qualified Data.Text.Lazy as T
    import qualified Data.Text.Lazy.IO as TI
    import Data.Map.Strict hiding (foldr, map, foldl')
    import System.Environment
    import System.IO
    import Data.Word
    
    dictionate :: [Text] -> Map Text Word16
    dictionate = fromListWith (+) . (`zip` [1,1..])
    
    main = do
        [file,out] <- getArgs
        h <- openFile file ReadMode
        hO <- openFile out WriteMode
        mapM_ (flip hSetEncoding utf8) [h,hO]
        txt <- TI.hGetContents h
        TI.hPutStr hO . T.unlines . 
          map (uncurry ((. cons '\t' . pack . show) . append)) . 
          toList . dictionate . T.words $ txt
        hFlush hO
        mapM_ hClose [h,hO]
        print "success"
    

我的方法出了什么问题?在时间和内存性能方面完成我想要做的最好的方法是什么?

2 个答案:

答案 0 :(得分:7)

此内存使用是预期的。 Data.Map.Map消耗约6N字的内存+密钥大小&amp;值(取自this excellent post by Johan Tibell的数据)。 懒惰 Texttakes up 7 words + 2*N bytes(舍入为机器字大小的倍数)和Word16 takes up two words(标头+有效负载)。我们假设一台64位机器,所以字大小为8字节。我们还假设输入中的平均字符串长度为8个字符。

考虑到这一切,内存使用的最终公式是6*N + 7*N + 2*N + 2*N字。

在最坏的情况下,所有单词都会有所不同,并且会有(6 * 1024^3)/8 ~= 800 * 10^6个单词。在上面的公式中插入我们得到的最坏情况的地图大小约。 102 GiB ,这似乎与实验结果一致。反方向求解此方程式告诉我们,您的文件包含大约200*10^6个不同的单词。

至于解决此问题的替代方法,请考虑使用trie(在评论中由J.Abrahamson建议)或近似方法,例如count-min sketch

答案 1 :(得分:0)

在传统数据处理的世界中,这个问题可以通过排序(如果需要外部在磁盘或磁贴上),然后扫描已排序的文件来计算分组的单词运行来完成。当然,您可以在排序的早期阶段进行一些部分缩减,以节省一些空间和时间。