Около 6 лет назад я сравнивал свои собственные синтаксические анализаторы в OCaml и обнаружил, что они были ~ 5 × медленнее, чем генераторы парсеров, предлагаемые в то время. Недавно я пересматривал эту тему и сравнивал Parsec Haskell с простым ручным списком precedence climbing parser, написанным на F #, и был удивлен тем, что F # был 25 × быстрее, чем Haskell.Комбинированные синтаксические анализаторы могут быть эффективными?
Вот код Haskell я читал большое математическое выражение из файла, разобрать и оценить его:
import Control.Applicative
import Text.Parsec hiding ((<|>))
expr = chainl1 term ((+) <$ char '+' <|> (-) <$ char '-')
term = chainl1 fact ((*) <$ char '*' <|> div <$ char '/')
fact = read <$> many1 digit <|> char '(' *> expr <* char ')'
eval :: String -> Int
eval = either (error . show) id . parse expr "" . filter (/= ' ')
main :: IO()
main = do
file <- readFile "expr"
putStr $ show $ eval file
putStr "\n"
и вот мой автономный старшинство восхождение парсер в F #:
let rec (|Expr|) = function
| P(f, xs) -> Expr(loop (' ', f, xs))
| xs -> invalidArg "Expr" (sprintf "%A" xs)
and loop = function
| ' ' as oop, f, ('+' | '-' as op)::P(g, xs)
| (' ' | '+' | '-' as oop), f, ('*' | '/' as op)::P(g, xs) ->
let h, xs = loop (op, g, xs)
match op with
| '+' -> (+) | '-' -> (-) | '*' -> (*) | '/' | _ -> (/)
|> fun op -> loop (oop, op f h, xs)
| _, f, xs -> f, xs
and (|P|_|) = function
| '('::Expr(f, ')'::xs) -> Some(P(f, xs))
| c::_ as xs when '0' <= c && c <= '9' ->
let rec loop n = function
| c2::xs when '0' <= c2 && c2 <= '9' -> loop (10*n + int(string c2)) xs
| xs -> Some(P(n, xs))
loop 0 xs
| _ -> None
Мое впечатление, что даже самые современные компиляторы парсера тратят много времени на отслеживание. Это верно? Если это так, можно ли написать компараторы парсеров, которые генерируют конечные машины для получения конкурентоспособной производительности или необходимо использовать генерацию кода?
EDIT:
Вот сценарий OCaml я использовал для создания выражения ~ 2Mb для сравнительного анализа:
open Printf
let rec f ff n =
if n=0 then fprintf ff "1" else
fprintf ff "%a+%a*(%a-%a)" f (n-1) f (n-1) f (n-1) f (n-1)
let() =
let n = try int_of_string Sys.argv.(1) with _ -> 3 in
fprintf stdout "%a\n" f n
Я всегда был под i что компараторы парсеров были неэффективны, но вам действительно нужно попробовать оба решения на одном языке, чтобы получить хороший показатель разницы в скорости. – Gabe
кажется, что вы используете parsec 3.x, который в соответствии с этим медленнее, чем parsec 2 http://www.mail-archive.com/[email protected]/msg81686.html –
Проблема, возможно, также , что вы используете списки символов в Haskell. Разбор строки Bytestring выполняется быстрее. – fuz