-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
perf: strictify Meta's ID field #1110
Conversation
This one-character change massively reduces our memory usage (measured by maximum residency) on some workloads. When running the testsuite with `--hedgehog-tests 0` (to get reproducible results) we see a (roughly) 15% drop in maximum residency (as measured by `+RTS -s`), and with the following patch to run a much bigger version of `EvalFull.unit_8`, we see a 97% drop, and the total memory in use drops from 1177MB to 58MB! It also has a small beneficial effect on the amount of memory copied during GC, and a negligible effect on both total memory allocated and elapsed runtimes. diff --git a/primer/test/Tests/EvalFull.hs b/primer/test/Tests/EvalFull.hs index c98878049..9292b2870 100644 --- a/primer/test/Tests/EvalFull.hs +++ b/primer/test/Tests/EvalFull.hs @@ -209,13 +209,13 @@ unit_7 = unit_8 :: Assertion unit_8 = - let n = 10 + let n = 100 e = mapEven n in do evalFullTest (maxID e) builtinTypes (defMap e) 500 Syn (expr e) >>= \case Left (TimedOut _) -> pure () x -> assertFailure $ show x - s <- evalFullTest (maxID e) builtinTypes (defMap e) 1000 Syn (expr e) + s <- evalFullTest (maxID e) builtinTypes (defMap e) 100000 Syn (expr e) s <~==> Right (expectedResult e) -- A worker/wrapper'd map Signed-off-by: Ben Price <[email protected]>
857ef55
to
305560d
Compare
I also attempted to add similar bangs on the metadata-containing fields of The diff of the above attempt```diff diff --git a/primer/src/Primer/Core.hs b/primer/src/Primer/Core.hs index 4e168de1e..0c0b80f42 100644 --- a/primer/src/Primer/Core.hs +++ b/primer/src/Primer/Core.hs @@ -149,17 +149,17 @@ type Expr = Expr' ExprMeta TypeMeta -- Most of the backend fixes a ~ b ~ ID. -- The typechecker produces a ~ (ID, Type' ()), b ~ ID. data Expr' a b - = Hole a (Expr' a b) -- See Note [Holes and bidirectionality] - | EmptyHole a - | Ann a (Expr' a b) (Type' b) - | App a (Expr' a b) (Expr' a b) - | APP a (Expr' a b) (Type' b) - | Con a ValConName [Expr' a b] -- See Note [Checkable constructors] - | Lam a LVarName (Expr' a b) - | LAM a TyVarName (Expr' a b) - | Var a TmVarRef + = Hole !a (Expr' a b) -- See Note [Holes and bidirectionality] + | EmptyHole !a + | Ann !a (Expr' a b) (Type' b) + | App !a (Expr' a b) (Expr' a b) + | APP !a (Expr' a b) (Type' b) + | Con !a ValConName [Expr' a b] -- See Note [Checkable constructors] + | Lam !a LVarName (Expr' a b) + | LAM !a TyVarName (Expr' a b) + | Var !a TmVarRef | Let - a + !a -- | bound variable LVarName -- | value the variable is bound to @@ -170,7 +170,7 @@ data Expr' a b -- It is currently only constructed automatically during evaluation - -- the student can't directly make it. LetType - a + !a -- | bound variable TyVarName -- | value the variable is bound to @@ -178,7 +178,7 @@ data Expr' a b -- | expression the binding scopes over (Expr' a b) | Letrec - a + !a -- | bound variable LVarName -- | value the variable is bound to; the variable itself is in scope, as this is a recursive let @@ -187,8 +187,8 @@ data Expr' a b (Type' b) -- | body of the let; binding scopes over this (Expr' a b) - | Case a (Expr' a b) [CaseBranch' a b] (CaseFallback' a b) -- See Note [Case] - | PrimCon a PrimCon + | Case !a (Expr' a b) [CaseBranch' a b] (CaseFallback' a b) -- See Note [Case] + | PrimCon !a PrimCon deriving stock (Eq, Show, Read, Data, Generic) deriving (FromJSON, ToJSON) via PrimerJSON (Expr' a b) deriving anyclass (NFData) diff --git a/primer/src/Primer/Core/Type.hs b/primer/src/Primer/Core/Type.hs index a3ddd97ba..429cc4573 100644 --- a/primer/src/Primer/Core/Type.hs +++ b/primer/src/Primer/Core/Type.hs @@ -41,18 +41,18 @@ type TypeMeta = Meta (Maybe (Kind' ()))-- | NB: Be careful with equality -- it is on-the-nose, rather than up-to-alpha: see Subst:alphaEqTy
@@ -80,9 +80,9 @@ type Kind = Kind' KindMeta data Kind' a
-- | Regenerate all IDs, not changing any other metadata regenerateTypeIDs' :: MonadFresh ID m => (ID -> a -> b) -> Type' a -> m (Type' b) -- | Adds 'ID's and trivial metadata
-- | Regenerate all IDs, not changing any other metadata regenerateExprIDs' :: MonadFresh ID m => (ID -> a -> a') -> (ID -> b -> b') -> Expr' a b -> m (Expr' a' b')
-- | Like 'generateTypeIDs', but for expressions unit_8 :: Assertion
-- A worker/wrapper'd map
|
data Meta a = Meta ID a (Maybe Value) | ||
data Meta a = Meta !ID a (Maybe Value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could someone independently confirm some time&space measurements for this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's just merge this -- easy enough to revert, and hopefully our benchmark suite will reproduce.
(I've always wondered why we haven't done this more aggressively across the entire code base.) |
This one-character change massively reduces our memory usage (measured by maximum residency) on some workloads. When running the testsuite with
--hedgehog-tests 0
(to get reproducible results) we see a (roughly) 15% drop in maximum residency (as measured by+RTS -s
), and with the following patch to run a much bigger version ofEvalFull.unit_8
, we see a 97% drop, and the total memory in use drops from 1177MB to 58MB! It also has a small beneficial effect on the amount of memory copied during GC, and a negligible effect on both total memory allocated and elapsed runtimes.