Skip to content

Commit 84c7e28

Browse files
committed
Parsing performance improvements
Parsing lists of things is no longer O(n^2). Fixed using the 'Reversed' newtype. Allocations stay roughly the same, time improvements are on the order of 3-7% Also tweaked tests to clear out WIP.json on every run (since it can never still be relevant)
1 parent 573ed00 commit 84c7e28

File tree

4 files changed

+51
-22
lines changed

4 files changed

+51
-22
lines changed

benchmarks/allocation-benchmarks/Main.hs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@
33
import Weigh
44

55
import Control.Monad (filterM)
6+
import Control.Exception (catch, throwIO)
67
import Data.Foldable (for_)
78
import Data.Traversable (for)
89
import GHC.Exts (fromString)
910

1011
import Language.Rust.Syntax (SourceFile)
1112
import Language.Rust.Parser (readInputStream, Span, parse')
1213

13-
import System.Directory (getCurrentDirectory, listDirectory, createDirectoryIfMissing, doesFileExist)
14+
import System.Directory (getCurrentDirectory, listDirectory, createDirectoryIfMissing, doesFileExist, removeFile)
1415
import System.FilePath ((</>), (<.>), takeFileName)
1516
import System.Process (proc, readCreateProcess)
17+
import System.IO.Error (isDoesNotExistError)
1618

1719
import Data.Aeson
1820
import qualified Data.ByteString.Lazy as BL
@@ -34,6 +36,10 @@ main = do
3436
entries <- map (sampleSources </>) <$> listDirectory sampleSources
3537
files <- filterM doesFileExist entries
3638

39+
-- Clear out previous WIP (if there is one)
40+
catch (removeFile (workingDirectory </> "allocations" </> "WIP" <.> "json"))
41+
(\e -> if isDoesNotExistError e then pure () else throwIO e)
42+
3743
-- Run 'weigh' tests
3844
fileStreams <- for files $ \file -> do { is <- readInputStream file; pure (takeFileName file, is) }
3945
let weigh = do setColumns [ Case, Max, Allocated, GCs, Live ]

benchmarks/timing-benchmarks/Main.hs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@ import Criterion.Types (anMean, reportAnalysis, timeLimit, anOutlierVar, ovEffec
66
import Statistics.Resampling.Bootstrap (Estimate(..))
77

88
import Control.Monad (filterM)
9+
import Control.Exception (catch, throwIO)
910
import Data.Traversable (for)
1011
import GHC.Exts (fromString)
1112

1213
import Language.Rust.Syntax (SourceFile)
1314
import Language.Rust.Parser (readInputStream, Span, parse')
1415

15-
import System.Directory (getCurrentDirectory, listDirectory, createDirectoryIfMissing, doesFileExist)
16+
import System.Directory (getCurrentDirectory, listDirectory, createDirectoryIfMissing, doesFileExist, removeFile)
1617
import System.FilePath ((</>), (<.>), takeFileName)
1718
import System.Process (proc, readCreateProcess)
19+
import System.IO.Error (isDoesNotExistError)
1820

1921
import Data.Aeson
2022
import qualified Data.ByteString.Lazy as BL
@@ -33,6 +35,10 @@ main = do
3335
entries <- map (sampleSources </>) <$> listDirectory sampleSources
3436
files <- filterM doesFileExist entries
3537

38+
-- Clear out previous WIP (if there is one)
39+
catch (removeFile (workingDirectory </> "timings" </> "WIP" <.> "json"))
40+
(\e -> if isDoesNotExistError e then pure () else throwIO e)
41+
3642
-- Run 'criterion' tests
3743
reports <- for files $ \f -> do
3844
let name = takeFileName f

src/Language/Rust/Parser/Internal.y

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ import Language.Rust.Data.Position
3737
import Language.Rust.Parser.Lexer (lexNonSpace, lexShebangLine)
3838
import Language.Rust.Parser.ParseMonad (pushToken, getPosition, P, parseError)
3939
import Language.Rust.Parser.Literals (translateLit)
40+
import Language.Rust.Parser.Reversed
4041

41-
import Data.List.NonEmpty (NonEmpty(..), (<|), toList)
42+
import Data.Foldable (toList)
43+
import Data.List.NonEmpty (NonEmpty(..), (<|))
4244
import qualified Data.List.NonEmpty as N
4345
import Data.Semigroup ((<>))
4446
import Text.Read (readMaybe)
@@ -323,8 +325,8 @@ gt :: { () }
323325
-------------
324326

325327
-- | One or more occurences of 'p'
326-
some(p) :: { NonEmpty a }
327-
: some(p) p { $1 |> $2 }
328+
some(p) :: { Reversed NonEmpty a }
329+
: some(p) p { let Reversed xs = $1 in Reversed ($2 <| xs) }
328330
| p { [$1] }
329331

330332
-- | Zero or more occurences of 'p'
@@ -333,8 +335,8 @@ many(p) :: { [a] }
333335
| {- empty -} { [] }
334336

335337
-- | One or more occurences of 'p', seperated by 'sep'
336-
sep_by1(p,sep) :: { NonEmpty a }
337-
: sep_by1(p,sep) sep p { $1 |> $3 }
338+
sep_by1(p,sep) :: { Reversed NonEmpty a }
339+
: sep_by1(p,sep) sep p { let Reversed xs = $1 in Reversed ($3 <| xs) }
338340
| p { [$1] }
339341

340342
-- | Zero or more occurrences of 'p', separated by 'sep'
@@ -343,7 +345,7 @@ sep_by(p,sep) :: { [a] }
343345
| {- empty -} { [] }
344346

345347
-- | One or more occurrences of 'p', seperated by 'sep', optionally ending in 'sep'
346-
sep_by1T(p,sep) :: { NonEmpty a }
348+
sep_by1T(p,sep) :: { Reversed NonEmpty a }
347349
: sep_by1(p,sep) sep { $1 }
348350
| sep_by1(p,sep) { $1 }
349351

@@ -540,7 +542,7 @@ ty_qual_path :: { Spanned (QSelf Span, Path Span) }
540542

541543
-- parse_path_segments_without_colons()
542544
path_segments_without_colons :: { Spanned (NonEmpty (Ident, PathParameters Span)) }
543-
: sep_by1(path_segment_without_colons, '::') { sequence $1 }
545+
: sep_by1(path_segment_without_colons, '::') { sequence (toNonEmpty $1) }
544546

545547
-- No corresponding function - see path_segments_without_colons
546548
path_segment_without_colons :: { Spanned (Ident, PathParameters Span) }
@@ -609,7 +611,7 @@ ty_general :: { Ty Span }
609611
-- All types, including trait types with plus
610612
ty :: { Ty Span }
611613
: ty_no_plus { $1 }
612-
| poly_trait_ref_mod_bound '+' sep_by1T(ty_param_bound_mod,'+') { TraitObject ($1 <| $3) ($1 # $3) }
614+
| poly_trait_ref_mod_bound '+' sep_by1T(ty_param_bound_mod,'+') { TraitObject ($1 <| toNonEmpty $3) ($1 # $3) }
613615

614616
-- parse_ty_no_plus()
615617
ty_no_plus :: { Ty Span }
@@ -621,7 +623,7 @@ ty_no_plus :: { Ty Span }
621623
ty_prim :: { Ty Span }
622624
: no_for_ty_prim { $1 }
623625
| for_ty_no_plus { $1 }
624-
| poly_trait_ref_mod_bound '+' sep_by1T(ty_param_bound_mod,'+') { TraitObject ($1 <| $3) ($1 # $3) }
626+
| poly_trait_ref_mod_bound '+' sep_by1T(ty_param_bound_mod,'+') { TraitObject ($1 <| toNonEmpty $3) ($1 # $3) }
625627

626628
-- All (non-sum) types not starting with a 'for'
627629
no_for_ty :: { Ty Span }
@@ -665,7 +667,7 @@ for_ty_no_plus :: { Ty Span }
665667
}
666668

667669
impl_ty :: { Ty Span }
668-
: impl sep_by1(ty_param_bound_mod,'+') %prec IMPLTRAIT { ImplTrait $2 ($1 # $2) }
670+
: impl sep_by1(ty_param_bound_mod,'+') %prec IMPLTRAIT { ImplTrait (toNonEmpty $2) ($1 # $2) }
669671

670672
-- An optional lifetime followed by an optional mutability
671673
lifetime_mut :: { (Maybe (Lifetime Span), Mutability) }
@@ -810,8 +812,8 @@ pat_tup :: { ([Pat Span], Maybe Int, Bool) }
810812
pat_slice :: { ([Pat Span], Maybe (Pat Span), [Pat Span]) }
811813
: sep_by1(pat,',') ',' '..' ',' sep_by1T(pat,',') { (toList $1, Just (WildP mempty), toList $5) }
812814
| sep_by1(pat,',') ',' '..' { (toList $1, Just (WildP mempty), []) }
813-
| sep_by1(pat,',') '..' ',' sep_by1T(pat,',') { (N.init $1, Just (N.last $1), toList $4) }
814-
| sep_by1(pat,',') '..' { (N.init $1, Just (N.last $1), []) }
815+
| sep_by1(pat,',') '..' ',' sep_by1T(pat,',') { let (xs, x) = unsnoc $1 in (toList xs, Just x, toList $4) }
816+
| sep_by1(pat,',') '..' { let (xs, x) = unsnoc $1 in (toList xs, Just x, []) }
815817
| sep_by1T(pat,',') { (toList $1, Nothing, []) }
816818
| '..' ',' sep_by1T(pat,',') { ([], Just (WildP mempty), toList $3) }
817819
| '..' { ([], Just (WildP mempty), []) }
@@ -1050,7 +1052,7 @@ arms :: { [Arm Span] }
10501052
: ntArm { [$1] }
10511053
| ntArm arms { $1 : $2 }
10521054
| many(outer_attribute) sep_by1(pat,'|') arm_guard '=>' expr_arms
1053-
{ let (e,as) = $> in (Arm $1 $2 $3 e ($1 # $2 # e) : as) }
1055+
{ let (e,as) = $> in (Arm $1 (toNonEmpty $2) $3 e ($1 # $2 # e) : as) }
10541056

10551057
arm_guard :: { Maybe (Expr Span) }
10561058
: {- empty -} { Nothing }
@@ -1453,14 +1455,14 @@ def :: { Spanned Defaultness }
14531455
| default { Spanned Default (spanOf $1) }
14541456
14551457
view_path :: { ViewPath Span }
1456-
: '::' sep_by1(self_or_ident,'::') { let n = fmap unspan $2 in ViewPathSimple True (N.init n) (PathListItem (N.last n) Nothing mempty) ($1 # $>) }
1457-
| '::' sep_by1(self_or_ident,'::') as ident { let n = fmap unspan $2 in ViewPathSimple True (N.init n) (PathListItem (N.last n) (Just (unspan $>)) mempty) ($1 # $>) }
1458+
: '::' sep_by1(self_or_ident,'::') { let (ns,n) = unsnoc (fmap unspan $2) in ViewPathSimple True (toList ns) (PathListItem n Nothing mempty) ($1 # $>) }
1459+
| '::' sep_by1(self_or_ident,'::') as ident { let (ns,n) = unsnoc (fmap unspan $2) in ViewPathSimple True (toList ns) (PathListItem n (Just (unspan $>)) mempty) ($1 # $>) }
14581460
| '::' '*' { ViewPathGlob True [] ($1 # $2) }
14591461
| '::' sep_by1(self_or_ident,'::') '::' '*' { ViewPathGlob True (fmap unspan (toList $2)) ($1 # $>) }
14601462
| '::' sep_by1(self_or_ident,'::') '::' '{' sep_byT(plist,',') '}' { ViewPathList True (map unspan (toList $2)) $5 ($1 # $>) }
14611463
| '::' '{' sep_byT(plist,',') '}' { ViewPathList True [] $3 ($1 # $>) }
1462-
| sep_by1(self_or_ident,'::') { let n = fmap unspan $1 in ViewPathSimple False (N.init n) (PathListItem (N.last n) Nothing mempty) ($1 # $>) }
1463-
| sep_by1(self_or_ident,'::') as ident { let n = fmap unspan $1 in ViewPathSimple False (N.init n) (PathListItem (N.last n) (Just (unspan $>)) mempty) ($1 # $>) }
1464+
| sep_by1(self_or_ident,'::') { let (ns,n) = unsnoc (fmap unspan $1) in ViewPathSimple False (toList ns) (PathListItem n Nothing mempty) ($1 # $>) }
1465+
| sep_by1(self_or_ident,'::') as ident { let (ns,n) = unsnoc (fmap unspan $1) in ViewPathSimple False (toList ns) (PathListItem n (Just (unspan $>)) mempty) ($1 # $>) }
14641466
| '*' { ViewPathGlob False [] (spanOf $1) }
14651467
| sep_by1(self_or_ident,'::') '::' '*' { ViewPathGlob False (fmap unspan (toList $1)) ($1 # $>) }
14661468
| sep_by1(self_or_ident,'::') '::' '{' sep_byT(plist,',') '}' { ViewPathList False (map unspan (toList $1)) $4 ($1 # $>) }

src/Language/Rust/Parser/Reversed.hs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,22 @@ along with the usual class instances.
1313
{-# LANGUAGE FlexibleInstances, TypeFamilies #-}
1414

1515
module Language.Rust.Parser.Reversed (
16-
Reversed(..), toNonEmpty
16+
Reversed(..), toNonEmpty, unsnoc
1717
) where
1818

1919
import Data.Foldable
20+
import Data.Semigroup
2021
import qualified Data.List.NonEmpty as N
2122
import qualified GHC.Exts as G
2223

24+
import Language.Rust.Data.Position
25+
2326
-- | Wrap a data type where all the operations are reversed
2427
newtype Reversed f a = Reversed (f a)
2528

29+
instance Functor f => Functor (Reversed f) where
30+
fmap f (Reversed xs) = Reversed (fmap f xs)
31+
2632
instance Foldable (Reversed []) where
2733
foldMap f (Reversed xs) = foldMap f (reverse xs)
2834
toList (Reversed xs) = reverse xs
@@ -31,6 +37,9 @@ instance Foldable (Reversed N.NonEmpty) where
3137
foldMap f (Reversed xs) = foldMap f (N.reverse xs)
3238
toList (Reversed xs) = reverse (toList xs)
3339

40+
instance Semigroup (f a) => Semigroup (Reversed f a) where
41+
Reversed xs <> Reversed ys = Reversed (ys <> xs)
42+
3443
instance Monoid (f a) => Monoid (Reversed f a) where
3544
mempty = Reversed mempty
3645
mappend (Reversed xs) (Reversed ys) = Reversed (mappend ys xs)
@@ -40,9 +49,15 @@ instance G.IsList (f a) => G.IsList (Reversed f a) where
4049
fromList xs = Reversed (G.fromList (reverse xs))
4150
toList (Reversed xs) = reverse (G.toList xs)
4251

52+
instance Located (f a) => Located (Reversed f a) where
53+
spanOf (Reversed xs) = spanOf xs
54+
4355
-- | Convert a reversed 'N.NonEmpty' back into a normal one.
56+
{-# INLINE toNonEmpty #-}
4457
toNonEmpty :: Reversed N.NonEmpty a -> N.NonEmpty a
4558
toNonEmpty (Reversed xs) = N.reverse xs
46-
47-
4859

60+
-- TODO
61+
{-# INLINE unsnoc #-}
62+
unsnoc :: Reversed N.NonEmpty a -> (Reversed [] a, a)
63+
unsnoc (Reversed (x N.:| xs)) = (Reversed xs, x)

0 commit comments

Comments
 (0)