Can parser combinators be made efficient?

后端 未结 4 1715
北恋
北恋 2020-12-22 22:35

Around 6 years ago, I benchmarked my own parser combinators in OCaml and found that they were ~5× slower than the parser generators on offer at the time. I recently rev

4条回答
  •  粉色の甜心
    2020-12-22 23:13

    I've come up with a Haskell solution that is 30× faster than the Haskell solution you posted (with my concocted test expression).

    Major changes:

    1. Change Parsec/String to Attoparsec/ByteString
    2. In the fact function, change read & many1 digit to decimal
    3. Made the chainl1 recursion strict (remove $! for the lazier version).

    I tried to keep everything else you had as similar as possible.

    import Control.Applicative
    import Data.Attoparsec
    import Data.Attoparsec.Char8
    import qualified Data.ByteString.Char8 as B
    
    expr :: Parser Int
    expr = chainl1 term ((+) <$ char '+' <|> (-) <$ char '-')
    
    term :: Parser Int
    term = chainl1 fact ((*) <$ char '*' <|> div <$ char '/')
    
    fact :: Parser Int
    fact = decimal <|> char '(' *> expr <* char ')'
    
    eval :: B.ByteString -> Int
    eval = either (error . show) id . eitherResult . parse expr . B.filter (/= ' ')
    
    chainl1 :: (Monad f, Alternative f) => f a -> f (a -> a -> a) -> f a
    chainl1 p op = p >>= rest where
      rest x = do f <- op
                  y <- p
                  rest $! (f x y)
               <|> pure x
    
    main :: IO ()
    main = B.readFile "expr" >>= (print . eval)
    

    I guess what I concluded from this is that the majority of the slowdown for the parser combinator was that it was sitting on an inefficient base, not that it was a parser combinator, per se.

    I imagine with more time and profiling this could go faster, as I stopped when I went past the 25× mark.

    I don't know if this would be faster than the precedence climbing parser ported to Haskell. Maybe that would be an interesting test?

提交回复
热议问题