В данный момент я экспериментирую с 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