Skip to content

Conversation

Radvendii
Copy link
Contributor

⚠️ built on top of #14090

Motivation

It's useful to know which Exprs are getting made, to know where to focus attention, and to sanity-check how much memory we save by different optimizations. It turns out for a NixOS config evaluation, the top-used Exprs are:

  • ExprVar (700K) (37%)
  • ExprAttrs (670K) (35%)
  • ExprString (370K) (19%)
  • ExprSelect (200K) (10%)
  • ExprCall (170K) (9%)
  • Total (1.89M)

⚠️ This... adds up to 110%, so we must be double-counting somewhere, but I can't find it. I'm only incrementing the counters in constructors, which should also call the Expr constructor and therefore increment the total.

I know ExprInheritFroms also count as ExprVars, but ExprInheritFroms didn't even make it into the top 5 list, so that doesn't explain the discrepancy.

Context

Relevant to my work tracked in #14088


Add 👍 to pull requests you find important.

The Nix maintainer team uses a GitHub project board to schedule and track reviews.

@Radvendii Radvendii requested a review from edolstra as a code owner September 28, 2025 17:51
@Radvendii Radvendii marked this pull request as draft September 28, 2025 17:51
1. Saves 24-32 bytes per string (size of std::string)
2. Saves additional bytes by not over-allocating strings (in total we
save ~1% memory)
3. Sets us up to perform a similar transformation on the other Expr
subclasses
4. Makes ExprString trivially moveable (before the string data might
move, causing the Value's pointer to become invalid). This is important
so we can put ExprStrings in an std::vector and refer to them by index

We have introduced a string copy in ParserState::stripIndentation().
This could be removed by pre-allocating the right sized string in the
arena, but this adds complexity and doesn't seem to improve performance,
so for now we've left the copy in.
Comment on lines +2959 to +2985
{"total", Expr::nrCreated.load()},
{"ExprInt", ExprInt::nrCreated.load()},
{"ExprFloat", ExprFloat::nrCreated.load()},
{"ExprString", ExprString::nrCreated.load()},
{"ExprPath", ExprPath::nrCreated.load()},
{"ExprVar", ExprVar::nrCreated.load()},
{"ExprInheritFrom", ExprInheritFrom::nrCreated.load()},
{"ExprSelect", ExprSelect::nrCreated.load()},
{"ExprOpHasAttr", ExprOpHasAttr::nrCreated.load()},
{"ExprAttr", ExprAttrs::nrCreated.load()},
{"ExprList", ExprList::nrCreated.load()},
{"ExprLambda", ExprLambda::nrCreated.load()},
{"ExprCall", ExprCall::nrCreated.load()},
{"ExprLet", ExprLet::nrCreated.load()},
{"ExprWith", ExprWith::nrCreated.load()},
{"ExprIf", ExprIf::nrCreated.load()},
{"ExprAssert", ExprAssert::nrCreated.load()},
{"ExprOpNot", ExprOpNot::nrCreated.load()},
{"ExprConcatStrings", ExprConcatStrings::nrCreated.load()},
{"ExprPos", ExprPos::nrCreated.load()},
{"ExprOpEq", ExprOpEq::nrCreated.load()},
{"ExprOpNEq", ExprOpNEq::nrCreated.load()},
{"ExprOpAnd", ExprOpAnd::nrCreated.load()},
{"ExprOpOr", ExprOpOr::nrCreated.load()},
{"ExprOpImpl", ExprOpImpl::nrCreated.load()},
{"ExprOpConcatLists", ExprOpConcatLists::nrCreated.load()},
{"ExprOpUpdate", ExprOpUpdate::nrCreated.load()},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like this could really benefit from some macro like NIX_VALUE_STORAGE_FOR_EACH_FIELD to make listing all the expr types less error-prone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants