使用haskell构建直方图,比使用python慢​​很多倍

时间:2012-03-19 14:34:39

标签: haskell

我打算测试朴素的贝叶斯分类。其中一部分是建立训练数据的直方图。问题是,我使用大量的培训数据,几年前就已经有了haskell-cafe邮件列表,文件夹中有超过20k的文件。

使用python创建直方图需要一两分钟,而使用haskell需要8分多钟。我正在使用Data.Map(insertWith'),枚举器和文本。我还能做些什么来加速该计划?

Haskell中:

import qualified Data.Text as T
import qualified Data.Text.IO as TI
import System.Directory
import Control.Applicative
import Control.Monad (filterM, foldM)
import System.FilePath.Posix ((</>))
import qualified Data.Map as M
import Data.Map (Map)
import Data.List (foldl')
import Control.Exception.Base (bracket)
import System.IO (Handle, openFile, hClose, hSetEncoding, IOMode(ReadMode), latin1)
import qualified Data.Enumerator as E
import Data.Enumerator (($$), (>==>), (<==<), (==<<), (>>==), ($=), (=$))
import qualified Data.Enumerator.List as EL
import qualified Data.Enumerator.Text as ET



withFile' ::  (Handle -> IO c) -> FilePath -> IO c
withFile' f fp = do
  bracket
    (do
      h ← openFile fp ReadMode
      hSetEncoding h latin1
      return h)
    hClose
    (f)

buildClassHistogram c = do
  files ← filterM doesFileExist =<< map (c </> ) <$> getDirectoryContents c
  foldM fileHistogram M.empty files

fileHistogram m file = withFile' (λh → E.run_ $ enumHist h) file
  where
    enumHist h = ET.enumHandle h $$ EL.fold (λm' l → foldl' (λm'' w → M.insertWith' (const (+1)) w 1 m'') m' $ T.words l) m

的Python:

for filename in listdir(root):
    filepath = root + "/" + filename
    # print(filepath)
    fp = open(filepath, "r", encoding="latin-1")
    for word in fp.read().split():
        if word in histogram:
            histogram[word] = histogram[word]+1
        else:
            histogram[word] = 1

修改:添加了导入

3 个答案:

答案 0 :(得分:8)

您可以尝试使用哈希表包中的命令式哈希映射:http://hackage.haskell.org/package/hashtables 我记得与Data.Map相比,我曾经获得了适度的加速。我不希望看到任何壮观的事。

<强>更新

我简化了你的python代码,所以我可以在一个大文件(1亿行)上测试它:

import sys
histogram={}
for word in sys.stdin.readlines():
    if word in histogram:
        histogram[word] = histogram[word]+1
    else:
        histogram[word] = 1
print histogram.get("the")

需要6.06秒

使用哈希表进行Haskell翻译:

{-# LANGUAGE OverloadedStrings #-}
import qualified Data.ByteString.Char8 as T
import  qualified Data.HashTable.IO as HT
main = do
  ls <- T.lines `fmap` T.getContents
  h <- HT.new :: IO (HT.BasicHashTable T.ByteString Int)
  flip mapM_ ls $ \w -> do
    r <- HT.lookup h w 
    case r of 
      Nothing -> HT.insert h w (1::Int)
      Just c  -> HT.insert h w (c+1)
  HT.lookup h "the" >>= print 

使用较大的分配区域运行:histogram +RTS -A500M 使用2.4%GC需要9.3秒。仍然比Python慢​​很多但不是太糟糕。

根据GHC user guide,您可以在编译时更改RTS选项:

  

GHC允许您在编译时更改程序的默认RTS选项   时间,使用-with-rtsopts标志(第4.12.6节,“影响的选项   链接”)。常见的用途是为您的程序提供默认值   堆和/或堆栈大小大于默认值。例如,   设置-H128m -K64m,链接-with-rtsopts =“ - H128m -K64m”。

答案 1 :(得分:7)

您的Haskell和Python实现正在使用具有不同复杂性的地图。 Python字典是哈希映射,因此每个操作(成员资格测试,查找和插入)的预期时间为O(1)。 Haskell版本使用Data.Map,它是一个平衡的二进制搜索树,因此相同的操作需要O(lg n)时间。如果你改变你的Haskell版本以使用不同的地图实现,比如哈希表或某种特里,它应该会更快。但是,我对实现这些数据结构的不同模块不太熟悉,以说哪个是最好的。我从the Data category on Hackage开始,寻找你喜欢的。您也可以查找允许STArray等破坏性更新的地图。

答案 2 :(得分:6)

我们需要更多信息:

  • 两个程序处理输入中的单词需要多长时间,没有用于维护计数的数据结构?

  • 有多少不同的单词,所以我们可以判断平衡树的额外log N成本是否需要考虑?

  • GHC的分析师说什么?特别是,分配花了多少时间? Haskell版本可能花费大部分时间来分配很快就过时的树节点。

  • 更新:我错过了小写的“文字”可能意味着Data.Text。您可能正在比较apply和oranges。 Python的Latin1编码每个字符使用一个字节。虽然它试图提高效率,但Data.Text必须允许超过256个字符的可能性。如果您切换到String或更好,Data.ByteString

  • 会发生什么情况

根据这些指标所说的,这里有几件事要尝试:

  • 如果分析输入是瓶颈,请尝试从Data.ByteString而不是Text开始所有I / O和分析。

  • 如果数据结构是瓶颈,Bentley和Sedgewick的ternary search trees纯粹是功能性的,但与哈希表竞争性地执行。 Hackage上有一个TernaryTrees包。