git.iko.soy/typeable/attoparsec
on master help sign in
Inlined Text.takeWhile and takeWhile1

Inlining these functions can improve code generation significantly when the predicate is statically known as it avoids boxing every parsed character. Unfortunately, marking `takeWhile` itself as `INLINEABLE` isn't sufficient to convince GHC to inline it. Moreover, we refactor things to avoid the allocations associated with the chunk accumulation list unless necessary. Previously things looked like, benchmarking takeWhile/T isAlpha time 32.33 μs (31.70 μs .. 33.25 μs) 0.994 R² (0.988 R² .. 0.998 R²) mean 33.02 μs (32.36 μs .. 33.92 μs) std dev 2.687 μs (2.086 μs .. 3.476 μs) variance introduced by outliers: 78% (severely inflated) benchmarking takeWhile/TL isAlpha time 71.00 μs (69.29 μs .. 72.83 μs) 0.991 R² (0.985 R² .. 0.996 R²) mean 71.60 μs (69.94 μs .. 74.58 μs) std dev 7.358 μs (5.428 μs .. 10.73 μs) variance introduced by outliers: 83% (severely inflated) benchmarking takeWhile1/T isAlpha time 34.64 μs (33.34 μs .. 36.26 μs) 0.989 R² (0.985 R² .. 0.994 R²) mean 34.93 μs (34.04 μs .. 35.91 μs) std dev 3.249 μs (2.821 μs .. 3.950 μs) variance introduced by outliers: 82% (severely inflated) benchmarking takeWhile1/TL isAlpha time 68.30 μs (66.59 μs .. 70.39 μs) 0.994 R² (0.991 R² .. 0.998 R²) mean 67.84 μs (66.53 μs .. 69.77 μs) std dev 5.139 μs (3.444 μs .. 7.793 μs) variance introduced by outliers: 73% (severely inflated) Now things look like, benchmarking takeWhile/T isAlpha time 12.22 μs (12.16 μs .. 12.31 μs) 1.000 R² (1.000 R² .. 1.000 R²) mean 12.29 μs (12.23 μs .. 12.36 μs) std dev 222.0 ns (180.4 ns .. 280.2 ns) variance introduced by outliers: 16% (moderately inflated) benchmarking takeWhile/TL isAlpha time 43.13 μs (42.77 μs .. 43.58 μs) 0.999 R² (0.998 R² .. 1.000 R²) mean 43.28 μs (42.85 μs .. 44.06 μs) std dev 1.894 μs (1.267 μs .. 3.137 μs) variance introduced by outliers: 48% (moderately inflated) benchmarking takeWhile1/T isAlpha time 13.37 μs (13.22 μs .. 13.57 μs) 0.999 R² (0.998 R² .. 0.999 R²) mean 13.29 μs (13.14 μs .. 13.44 μs) std dev 536.0 ns (463.2 ns .. 641.2 ns) variance introduced by outliers: 48% (moderately inflated) benchmarking takeWhile1/TL isAlpha time 42.33 μs (42.18 μs .. 42.59 μs) 0.999 R² (0.998 R² .. 0.999 R²) mean 43.72 μs (43.22 μs .. 44.45 μs) std dev 1.957 μs (1.491 μs .. 2.486 μs) variance introduced by outliers: 50% (moderately inflated) Moreover, this produces a similar >3x speedup in an HTML5 parser that I have.

commitc49ba29f5a88300905b694cc0572d1e45221fb94 authorBen Gamari <ben@smart-cactus.org> dateWed 6 Apr 2016, 13:04 (11 years ago) parent7b587a6
2 files changed+29−7
Data/Attoparsec/ByteString/Internal.hs+14−4 Data/Attoparsec/Text/Internal.hs+15−3
Data/Attoparsec/ByteString/Internal.hs+14−4
@@ -257,15 +257,24 @@ takeTill p = takeWhile (not . p)
257257-- parsers loop until a failure occurs. Careless use will thus result
258258-- in an infinite loop.
259259takeWhile :: (Word8 -> Bool) -> Parser ByteString
260−takeWhile p = concatReverse `fmap` go []
260+takeWhile p = do
261+ s <- B8.takeWhile p <$> get
262+ continue <- inputSpansChunks (B.length s)
263+ if continue
264+ then takeWhileAcc p [s]
265+ else return s
266+{-# INLINE takeWhile #-}
267+
268+takeWhileAcc :: (Word8 -> Bool) -> [ByteString] -> Parser ByteString
269+takeWhileAcc p = go
261270 where
262271 go acc = do
263272 s <- B8.takeWhile p <$> get
264273 continue <- inputSpansChunks (B.length s)
265274 if continue
266275 then go (s:acc)
267− else return (s:acc)
268−{-# INLINE takeWhile #-}
276+ else return $ concatReverse (s:acc)
277+{-# INLINE takeWhileAcc #-}
269278
270279takeRest :: Parser [ByteString]
271280takeRest = go []
@@ -355,8 +364,9 @@ takeWhile1 p = do
355364 advance len
356365 eoc <- endOfChunk
357366 if eoc
358− then (s<>) `fmap` takeWhile p
367+ then takeWhileAcc p [s]
359368 else return s
369+{-# INLINE takeWhile1 #-}
360370
361371-- | Match any byte in a set.
362372--
Data/Attoparsec/Text/Internal.hs+15−3
@@ -269,14 +269,25 @@ takeTill p = takeWhile (not . p)
269269-- parsers loop until a failure occurs. Careless use will thus result
270270-- in an infinite loop.
271271takeWhile :: (Char -> Bool) -> Parser Text
272−takeWhile p = concatReverse `fmap` go []
272+takeWhile p = do
273+ h <- T.takeWhile p <$> get
274+ continue <- inputSpansChunks (size h)
275+ -- only use slow concat path if necessary
276+ if continue
277+ then takeWhileAcc p [h]
278+ else return h
279+{-# INLINE takeWhile #-}
280+
281+takeWhileAcc :: (Char -> Bool) -> [Text] -> Parser Text
282+takeWhileAcc p = go
273283 where
274284 go acc = do
275285 h <- T.takeWhile p <$> get
276286 continue <- inputSpansChunks (size h)
277287 if continue
278288 then go (h:acc)
279− else return (h:acc)
289+ else return $ concatReverse (h:acc)
290+{-# INLINE takeWhileAcc #-}
280291
281292takeRest :: Parser [Text]
282293takeRest = go []
@@ -358,8 +369,9 @@ takeWhile1 p = do
358369 advance size'
359370 eoc <- endOfChunk
360371 if eoc
361− then (h<>) `fmap` takeWhile p
372+ then takeWhileAcc p [h]
362373 else return h
374+{-# INLINE takeWhile1 #-}
363375
364376-- | Match any character in a set.
365377--