Оптимизация синтаксического анализатора Haskell XML

В данный момент я экспериментирую с Haskell, и мне очень нравится этот опыт, но я оцениваю его для реального проекта с некоторыми довольно строгими требованиями к производительности. Первый этап моей задачи - обработать полный (без истории) дамп Википедии (bzip) - всего около 6 ГБ в сжатом виде. В python сценарий для полного извлечения каждой необработанной страницы (всего около 10 миллионов) занимает около 30 минут в моем ящике (а для справки реализация scala с использованием синтаксического анализатора запроса занимает около 40 минут). Я пытался воспроизвести эту производительность с помощью Haskell и ghc и изо всех сил пытался соответствовать этому.

Я использовал Codec.Compression.BZip для распаковки и hexpat для синтаксического анализа. Я использую ленивые строки байтов в качестве входных данных для hexpat и строгие строки байтов для типа текста элемента. И чтобы извлечь текст для каждой страницы, я создаю Dlist указателей на текстовые элементы, а затем повторяю его, чтобы выгрузить его в стандартный вывод. Код, который я только что описал, уже прошел через ряд итераций профилирования / рефакторинга (я быстро перешел от строк к строкам байтов, затем от конкатенации строк к спискам указателей на текст - затем к спискам указателей на текст). Я думаю, что у меня есть ускорение примерно на 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
7
задан Guy Coder 15 December 2013 в 15:01
поделиться