git.iko.soy/typeable/attoparsec
on master help sign in
Improve performance of numeric parsers

by parsing to Scientific numbers

commit3f15e46de982c9ec5a02c0c7473bba7e1630c52f authorBas van Dijk <v.dijk.bas@gmail.com> dateSat 28 Sep 2013, 20:19 (13 years ago) parentbff30e3
7 files changed+127−93
.gitignore+2 Data/Attoparsec/ByteString/Char8.hs+34−44 Data/Attoparsec/Number.hs+1−4 Data/Attoparsec/Text.hs+33−43 attoparsec.cabal+2−1 benchmarks/Benchmarks.hs+54 benchmarks/attoparsec-benchmarks.cabal+1−1
.gitignore+2
@@ -1,3 +1,5 @@
11*.hi
22*.o
33/dist/
4+.cabal-sandbox
5+cabal.sandbox.config
Data/Attoparsec/ByteString/Char8.hs+34−44
@@ -106,7 +106,7 @@ module Data.Attoparsec.ByteString.Char8
106106 , I.atEnd
107107 ) where
108108
109−import Control.Applicative ((*>), (<*), (<$>), (<|>))
109+import Control.Applicative (pure, (*>), (<*), (<$>), (<|>))
110110import Data.Attoparsec.ByteString.FastSet (charClass, memberChar)
111111import Data.Attoparsec.ByteString.Internal (Parser, (<?>))
112112import Data.Attoparsec.Combinator
@@ -114,8 +114,8 @@ import Data.Attoparsec.Number (Number(..))
114114import Data.Bits (Bits, (.|.), shiftL)
115115import Data.ByteString.Internal (c2w, w2c)
116116import Data.Int (Int8, Int16, Int32, Int64)
117−import Data.Ratio ((%))
118117import Data.String (IsString(..))
118+import Data.Scientific (Scientific, scientific, coefficient, base10Exponent)
119119import Data.Word (Word8, Word16, Word32, Word64, Word)
120120import Prelude hiding (takeWhile)
121121import qualified Data.Attoparsec.ByteString as A
@@ -471,8 +471,8 @@ rational :: Fractional a => Parser a
471471{-# SPECIALIZE rational :: Parser Double #-}
472472{-# SPECIALIZE rational :: Parser Float #-}
473473{-# SPECIALIZE rational :: Parser Rational #-}
474−rational = floaty $ \real frac fracDenom -> fromRational $
475− real % 1 + frac % fracDenom
474+{-# SPECIALIZE rational :: Parser Scientific #-}
475+rational = scientifically realToFrac
476476
477477-- | Parse a rational number.
478478--
@@ -490,12 +490,7 @@ rational = floaty $ \real frac fracDenom -> fromRational $
490490-- This function does not accept string representations of \"NaN\" or
491491-- \"Infinity\".
492492double :: Parser Double
493−double = floaty asDouble
494−
495−asDouble :: Integer -> Integer -> Integer -> Double
496−asDouble real frac fracDenom =
497− fromIntegral real + fromIntegral frac / fromIntegral fracDenom
498−{-# INLINE asDouble #-}
493+double = rational
499494
500495-- | Parse a number, attempting to preserve both speed and precision.
501496--
@@ -507,43 +502,38 @@ asDouble real frac fracDenom =
507502-- 'rational'.
508503--
509504-- This function does not accept string representations of \"NaN\" or
510−-- \"Infinity\".
505+-- \"
511506number :: Parser Number
512−number = floaty $ \real frac fracDenom ->
513− if frac == 0 && fracDenom == 0
514− then I real
515− else D (asDouble real frac fracDenom)
516−{-# INLINE number #-}
517−
518−data T = T !Integer !Int
519−
520−floaty :: Fractional a => (Integer -> Integer -> Integer -> a) -> Parser a
521−{-# INLINE floaty #-}
522−floaty f = do
507+number = scientifically $ \s ->
508+ let e = base10Exponent s
509+ c = coefficient s
510+ in if e >= 0
511+ then I (c * 10 ^ e)
512+ else D (fromInteger c / 10 ^ negate e)
513+
514+{-# INLINE scientifically #-}
515+scientifically :: (Scientific -> a) -> Parser a
516+scientifically h = do
523517 let minus = 45
524518 plus = 43
525519 !positive <- ((== plus) <$> I.satisfy (\c -> c == minus || c == plus)) <|>
526− return True
527− real <- decimal
528− let tryFraction = do
529− let dot = 46
530− _ <- I.satisfy (==dot)
531− ds <- I.takeWhile isDigit_w8
532− case I.parseOnly decimal ds of
533− Right n -> return $ T n (B.length ds)
534− _ -> fail "no digits after decimal"
535− T fraction fracDigits <- tryFraction <|> return (T 0 0)
520+ pure True
521+
522+ n <- decimal
523+
524+ let f fracDigits = scientific (B8.foldl' step n fracDigits)
525+ (negate $ B8.length fracDigits)
526+ step a w = a * 10 + fromIntegral (w - 48)
527+
528+ s <- let dot = 46 in
529+ (I.satisfy (==dot) *> (f <$> I.takeWhile isDigit_w8)) <|>
530+ pure (scientific n 0)
531+
532+ let !signedCoeff | positive = coefficient s
533+ | otherwise = negate $ coefficient s
534+
536535 let littleE = 101
537536 bigE = 69
538− e w = w == littleE || w == bigE
539− power <- (I.satisfy e *> signed decimal) <|> return (0::Int)
540− let n = if fracDigits == 0
541− then if power == 0
542− then fromIntegral real
543− else fromIntegral real * (10 ^^ power)
544− else if power == 0
545− then f real fraction (10 ^ fracDigits)
546− else f real fraction (10 ^ fracDigits) * (10 ^^ power)
547− return $ if positive
548− then n
549− else -n
537+ (I.satisfy (\c -> c == littleE || c == bigE) *>
538+ fmap (h . scientific signedCoeff . (base10Exponent s +)) (signed decimal)) <|>
539+ return (h $ scientific signedCoeff (base10Exponent s))
Data/Attoparsec/Number.hs+1−4
@@ -10,10 +10,7 @@
1010--
1111-- A simple number type, useful for parsing both exact and inexact
1212-- quantities without losing much precision.
13−module Data.Attoparsec.Number
14− (
15− Number(..)
16− ) where
13+module Data.Attoparsec.Number ( Number(..) ) where
1714
1815import Control.DeepSeq (NFData(rnf))
1916import Data.Data (Data)
Data/Attoparsec/Text.hs+33−43
@@ -103,14 +103,14 @@ module Data.Attoparsec.Text
103103 , I.atEnd
104104 ) where
105105
106−import Control.Applicative ((<$>), (*>), (<*), (<|>))
106+import Control.Applicative (pure, (<$>), (*>), (<*), (<|>))
107107import Data.Attoparsec.Combinator
108108import Data.Attoparsec.Number (Number(..))
109+import Data.Scientific (Scientific, scientific, coefficient, base10Exponent)
109110import Data.Attoparsec.Text.Internal ((<?>), Parser, Result, parse, takeWhile1)
110111import Data.Bits (Bits, (.|.), shiftL)
111112import Data.Char (isAlpha, isDigit, isSpace, ord)
112113import Data.Int (Int8, Int16, Int32, Int64)
113−import Data.Ratio ((%))
114114import Data.Text (Text)
115115import Data.Word (Word8, Word16, Word32, Word64, Word)
116116import qualified Data.Attoparsec.Internal as I
@@ -343,8 +343,8 @@ rational :: Fractional a => Parser a
343343{-# SPECIALIZE rational :: Parser Double #-}
344344{-# SPECIALIZE rational :: Parser Float #-}
345345{-# SPECIALIZE rational :: Parser Rational #-}
346−rational = floaty $ \real frac fracDenom -> fromRational $
347− real % 1 + frac % fracDenom
346+{-# SPECIALIZE rational :: Parser Scientific #-}
347+rational = scientifically realToFrac
348348
349349-- | Parse a rational number.
350350--
@@ -362,12 +362,7 @@ rational = floaty $ \real frac fracDenom -> fromRational $
362362-- This function does not accept string representations of \"NaN\" or
363363-- \"Infinity\".
364364double :: Parser Double
365−double = floaty asDouble
366−
367−asDouble :: Integer -> Integer -> Integer -> Double
368−asDouble real frac fracDenom =
369− fromIntegral real + fromIntegral frac / fromIntegral fracDenom
370−{-# INLINE asDouble #-}
365+double = rational
371366
372367-- | Parse a number, attempting to preserve both speed and precision.
373368--
@@ -381,11 +376,34 @@ asDouble real frac fracDenom =
381376-- This function does not accept string representations of \"NaN\" or
382377-- \"Infinity\".
383378number :: Parser Number
384−number = floaty $ \real frac fracDenom ->
385− if frac == 0 && fracDenom == 0
386− then I real
387− else D (asDouble real frac fracDenom)
388−{-# INLINE number #-}
379+number = scientifically $ \s ->
380+ let e = base10Exponent s
381+ c = coefficient s
382+ in if e >= 0
383+ then I (c * 10 ^ e)
384+ else D (fromInteger c / 10 ^ negate e)
385+
386+{-# INLINE scientifically #-}
387+scientifically :: (Scientific -> a) -> Parser a
388+scientifically h = do
389+ !positive <- ((== '+') <$> I.satisfy (\c -> c == '-' || c == '+')) <|>
390+ pure True
391+
392+ n <- decimal
393+
394+ let f fracDigits = scientific (T.foldl' step n fracDigits)
395+ (negate $ T.length fracDigits)
396+ step a c = a * 10 + fromIntegral (ord c - 48)
397+
398+ s <- (I.satisfy (=='.') *> (f <$> I.takeWhile isDigit)) <|>
399+ pure (scientific n 0)
400+
401+ let !signedCoeff | positive = coefficient s
402+ | otherwise = negate $ coefficient s
403+
404+ (I.satisfy (\c -> c == 'e' || c == 'E') *>
405+ fmap (h . scientific signedCoeff . (base10Exponent s +)) (signed decimal)) <|>
406+ return (h $ scientific signedCoeff (base10Exponent s))
389407
390408-- | Parse a single digit, as recognised by 'isDigit'.
391409digit :: Parser Char
@@ -433,31 +451,3 @@ s .*> f = I.string s *> f
433451-- | Type-specialized version of '<*' for 'Text'.
434452(<*.) :: Parser a -> Text -> Parser a
435453f <*. s = f <* I.string s
436−
437−data T = T !Integer !Int
438−
439−floaty :: Fractional a => (Integer -> Integer -> Integer -> a) -> Parser a
440−{-# INLINE floaty #-}
441−floaty f = do
442− !positive <- ((== '+') <$> I.satisfy (\c -> c == '-' || c == '+')) <|>
443− return True
444− real <- decimal
445− let tryFraction = do
446− _ <- I.satisfy (=='.')
447− ds <- I.takeWhile isDigit
448− case I.parseOnly decimal ds of
449− Right n -> return $ T n (T.length ds)
450− _ -> fail "no digits after decimal"
451− T fraction fracDigits <- tryFraction <|> return (T 0 0)
452− let e c = c == 'e' || c == 'E'
453− power <- (I.satisfy e *> signed decimal) <|> return (0::Int)
454− let n = if fracDigits == 0
455− then if power == 0
456− then fromIntegral real
457− else fromIntegral real * (10 ^^ power)
458− else if power == 0
459− then f real fraction (10 ^ fracDigits)
460− else f real fraction (10 ^ fracDigits) * (10 ^^ power)
461− return $ if positive
462− then n
463− else -n
attoparsec.cabal+2−1
@@ -43,7 +43,8 @@ library
4343 bytestring,
4444 containers,
4545 deepseq,
46− text >= 0.11.1.5
46+ text >= 0.11.1.5,
47+ scientific >= 0.0 && < 0.1
4748
4849 exposed-modules: Data.Attoparsec
4950 Data.Attoparsec.ByteString
benchmarks/Benchmarks.hs+54
@@ -81,6 +81,60 @@ main = do
8181 bench "short" $ nf (AB.parse quotedString) (BC.pack "abcdefghijk\"")
8282 , bench "long" $ nf (AB.parse quotedString) b
8383 ]
84+
85+ , let strN = "1234.56789"
86+ strNePos = "1234.56789e3"
87+ strNeNeg = "1234.56789e-3"
88+ in
89+ bgroup "numbers"
90+ [ let !tN = T.pack strN
91+ !tNePos = T.pack strNePos
92+ !tNeNeg = T.pack strNeNeg
93+ in bgroup "Text"
94+ [
95+ bgroup "no power"
96+ [ bench "double" $ nf (AT.parseOnly AT.double) tN
97+ , bench "number" $ nf (AT.parseOnly AT.number) tN
98+ , bench "rational" $ nf (AT.parseOnly (AT.rational :: AT.Parser Rational)) tN
99+ , bench "scientific" $ nf (AT.parseOnly (AT.rational :: AT.Parser Scientifc)) tN
100+ ]
101+ , bgroup "positive power"
102+ [ bench "double" $ nf (AT.parseOnly AT.double) tNePos
103+ , bench "number" $ nf (AT.parseOnly AT.number) tNePos
104+ , bench "rational" $ nf (AT.parseOnly (AT.rational :: AT.Parser Rational)) tNePos
105+ , bench "scientific" $ nf (AT.parseOnly (AT.rational :: AT.Parser Scientifc)) tNePos
106+ ]
107+ , bgroup "negative power"
108+ [ bench "double" $ nf (AT.parseOnly AT.double) tNeNeg
109+ , bench "number" $ nf (AT.parseOnly AT.number) tNeNeg
110+ , bench "rational" $ nf (AT.parseOnly (AT.rational :: AT.Parser Rational)) tNeNeg
111+ , bench "scientific" $ nf (AT.parseOnly (AT.rational :: AT.Parser Scientifc)) tNeNeg
112+ ]
113+ ]
114+ , let !bN = BC.pack strN
115+ !bNePos = BC.pack strNePos
116+ !bNeNeg = BC.pack strNeNeg
117+ in bgroup "ByteString"
118+ [ bgroup "no power"
119+ [ bench "double" $ nf (AC.parseOnly AC.double) bN
120+ , bench "number" $ nf (AC.parseOnly AC.number) bN
121+ , bench "rational" $ nf (AC.parseOnly (AC.rational :: AC.Parser Rational)) bN
122+ , bench "scientific" $ nf (AC.parseOnly (AC.rational :: AC.Parser Scientific)) bN
123+ ]
124+ , bgroup "positive power"
125+ [ bench "double" $ nf (AC.parseOnly AC.double) bNePos
126+ , bench "number" $ nf (AC.parseOnly AC.number) bNePos
127+ , bench "rational" $ nf (AC.parseOnly (AC.rational :: AC.Parser Rational)) bNePos
128+ , bench "scientific" $ nf (AC.parseOnly (AC.rational :: AC.Parser Scientific)) bNePos
129+ ]
130+ , bgroup "negative power"
131+ [ bench "double" $ nf (AC.parseOnly AC.double) bNeNeg
132+ , bench "number" $ nf (AC.parseOnly AC.number) bNeNeg
133+ , bench "rational" $ nf (AC.parseOnly (AC.rational :: AC.Parser Rational)) bNeNeg
134+ , bench "scientific" $ nf (AC.parseOnly (AC.rational :: AC.Parser Scientific)) bNeNeg
135+ ]
136+ ]
137+ ]
84138 ]
85139
86140-- Benchmarks bind and (potential) bounds-check merging.
benchmarks/attoparsec-benchmarks.cabal+1−1
@@ -13,6 +13,6 @@ executable attoparsec-benchmarks
1313 base,
1414 bytestring,
1515 criterion >= 0.5,
16− deepseq == 1.1.*,
16+ deepseq >= 1.1,
1717 parsec >= 3.1.2,
1818 text