我目前正在尝试使用Haskell,非常喜欢这种体验,但是我要在一个有一些严格性能要求的真实项目中评估它。我的第一个任务是处理完整的维基百科转储(bzipped),总计约6GB压缩。在Python中,一个脚本来提取每个原始页面(总共约1000万个)需要大约30分钟(参考Scala实现使用pull解析器需要约40分钟)。我一直在尝试使用Haskell和ghc复制此性能,并一直努力匹配它。
我一直在使用Codec.Compression.BZip进行解压缩,使用hexpat进行解析。我使用惰性bytestrings作为hexpat的输入,使用严格bytestrings作为元素文本类型。为了提取每个页面的文本,我正在建立指向文本元素的Dlist,然后迭代此列表将其转储到stdout。我刚刚描述的代码已经通过多次分析/重构迭代(我很快从字符串移动到了bytestrings,然后从字符串连接移动到了指向文本的列表 - 然后是指向文本的dlists)。我认为我已经从原始代码中获得了约2个数量级的加速,但它仍需要超过一个半小时才能解析(虽然它具有可爱的小内存占用)。因此,我正在寻找社区的一些启示,以帮助我更进一步。代码如下(我将其分解为多个子函数,以便从分析器中获取更多详细信息)。请原谅我的Haskell-我只编码了几天(曾经用Real World Haskell花了一周时间)。提前感谢您!
我一直在使用Codec.Compression.BZip进行解压缩,使用hexpat进行解析。我使用惰性bytestrings作为hexpat的输入,使用严格bytestrings作为元素文本类型。为了提取每个页面的文本,我正在建立指向文本元素的Dlist,然后迭代此列表将其转储到stdout。我刚刚描述的代码已经通过多次分析/重构迭代(我很快从字符串移动到了bytestrings,然后从字符串连接移动到了指向文本的列表 - 然后是指向文本的dlists)。我认为我已经从原始代码中获得了约2个数量级的加速,但它仍需要超过一个半小时才能解析(虽然它具有可爱的小内存占用)。因此,我正在寻找社区的一些启示,以帮助我更进一步。代码如下(我将其分解为多个子函数,以便从分析器中获取更多详细信息)。请原谅我的Haskell-我只编码了几天(曾经用Real World Haskell花了一周时间)。提前感谢您!
import System.Exit
import Data.Maybe
import Data.List
import Data.DList (DList)
import qualified Data.DList as DList
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Lazy as LazyByteString
import qualified Codec.Compression.BZip as BZip
import Text.XML.Expat.Proc
import Text.XML.Expat.Tree
import Text.XML.Expat.Format
testFile = "../data/enwiki-latest-pages-articles.xml.bz2"
validPage pageData = case pageData of
(Just _, Just _) -> True
(_, _) -> False
scanChildren :: [UNode ByteString] -> DList ByteString
scanChildren c = case c of
h:t -> DList.append (getContent h) (scanChildren t)
[] -> DList.fromList []
getContent :: UNode ByteString -> DList ByteString
getContent treeElement =
case treeElement of
(Element name attributes children) -> scanChildren children
(Text text) -> DList.fromList [text]
rawData t = ((getContent.fromJust.fst) t, (getContent.fromJust.snd) t)
extractText page = do
revision <- findChild (BS.pack "revision") page
text <- findChild (BS.pack "text") revision
return text
pageDetails tree =
let pageNodes = filterChildren relevantChildren tree in
let getPageData page = (findChild (BS.pack "title") page, extractText page) in
map rawData $ filter validPage $ map getPageData pageNodes
where
relevantChildren node = case node of
(Element name attributes children) -> name == (BS.pack "page")
(Text _) -> False
outputPages pagesText = do
let flattenedPages = map DList.toList pagesText
mapM_ (mapM_ BS.putStr) flattenedPages
readCompressed fileName = fmap BZip.decompress (LazyByteString.readFile fileName)
parseXml byteStream = parse defaultParseOptions byteStream :: (UNode ByteString, Maybe XMLParseError)
main = do
rawContent <- readCompressed testFile
let (tree, mErr) = parseXml rawContent
let pages = pageDetails tree
let pagesText = map snd pages
outputPages pagesText
putStrLn "Complete!"
exitWith ExitSuccess