-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
internal: Build source map for hir_def::TypeRef
s
#18074
Conversation
☔ The latest upstream changes (presumably #18075) made this pull request unmergeable. Please resolve the merge conflicts. |
9af921a
to
3c34665
Compare
Okay, this is ready for review. I intend to submit a follow-up PR with few cleanups that I don't want to insert here to not increase the size of this already-large PR. |
hir_def::TypeRef
shir_def::TypeRef
s
Whew that's quite a bit 😞 Wonder if we can shrink |
} | ||
|
||
/// A single generic argument. | ||
#[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
pub enum GenericArg { | ||
Type(TypeRef), | ||
Type(TypeRefId), | ||
Lifetime(LifetimeRef), |
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.
Aside: I imagine we might wanna give lifetimes the same treatment (given they also only appear in type position anyways)
I guess part of the memory increase comes from the new source map queries not being LRU'd. Gonna think a bit aabout whether we can reduce the memory impact of this change to some degree (aside from LRUing those queries) |
Can we make |
The split exists for incrementality purposes, and making that one transparent won't really save much given it just clones the |
If anything, it's the opposite: we can make the source maps query transparent (not just this one), since everyone that wants the source map is not perf-sensitive (IDE layer). |
What is the difference between LRU'd and not LRU'd queries? |
I'll try to find how each type consumes memory, and as a result of this analysis what can we do to shrink it. |
LRU'd ones will just have their caches evicted if they overflow the LRU limit (up to the limit). |
@Veykril Adding |
3c34665
to
9a2a0a2
Compare
☔ The latest upstream changes (presumably #18239) made this pull request unmergeable. Please resolve the merge conflicts. |
9a2a0a2
to
e8d7a19
Compare
☔ The latest upstream changes (presumably #18278) made this pull request unmergeable. Please resolve the merge conflicts. |
e8d7a19
to
9a4b995
Compare
be029b9
to
a4b350f
Compare
@Veykril After long time of trying to reduce the memory effect of this, I was able to reduce it down to 84mb. The first commit remains untouched (minus rebases), follow-up commits reduce memory usage. The two last specifically have somewhat bad ratio for code they touch versus their memory usage impact (and they also use unsafe code), so you may not want to include them. There is one commit (the one about attributes) that does an unrelated improvement, but all others are because of this PR. I believe I pretty much hit the end of the ability to optimize this (and introspection shows at least some amount is inherent to this, e.g. lack of interning and having to store source maps). I can optimize further, but the changes requires will be pervasive and the compensation will be very low (I estimate up to 3 megabytes for each change). I don't think this is worth it. I think that at this point, we need to bite the bullet and accept that this is a necessary evil. Also, do we have a way to run Miri on CI? |
9deffe3
to
b2b6e4d
Compare
☔ The latest upstream changes (presumably #18350) made this pull request unmergeable. Please resolve the merge conflicts. |
b2b6e4d
to
f8b674a
Compare
☔ The latest upstream changes (presumably #18362) made this pull request unmergeable. Please resolve the merge conflicts. |
f8b674a
to
ce82b01
Compare
☔ The latest upstream changes (presumably #18264) made this pull request unmergeable. Please resolve the merge conflicts. |
@@ -532,10 +538,11 @@ impl ExternCrateDeclData { | |||
pub struct ConstData { | |||
/// `None` for `const _: () = ();` | |||
pub name: Option<Name>, | |||
pub type_ref: Interned<TypeRef>, | |||
pub type_ref: TypeRefId, |
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.
Question for this, and related structs here, but shouldn't this type_ref always be the 0
ID? (if not, we could probably make it so) in which case we can omit storing it here
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.
It is not (it is always the last actually because typerefs are allocated recursively), but the last is also easy to tell. I do wonder if we should make it so dependent on implicit behavior however, or maybe reserve the first to it somehow.
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.
So I tried this and this doesn't reduce memory usage at all. On some types this doesn't even shrink them because of padding. And this depends on subtle implementation detail, and, if we take this to the extreme (i.e. apply this for as many types as possible, e.g. impls) it introduces an order dependency between the type ref lowering which isn't clear from a glance and needs to be documented. As such, I'm inclined to not do this.
Macro(AstId<ast::MacroCall>), | ||
Error, | ||
} | ||
|
||
#[cfg(target_arch = "x86_64")] | ||
const _: () = assert!(size_of::<TypeRef>() == 16); |
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.
I wonder if asking for 24
might be better, 16 seems to requires us to be very aggressive with boxing things which might spent equal amount of memory on allocator overhead (we can check that as a follow up to the PR though)
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.
I benchmarked memory usage using jemalloc, which I think includes the allocator overhead. Speed could be affected, but I don't think it will (nor did my benchmarks showed), since boxed types account for less than 10% of all types. If we allow 24 bytes we are able to unbox Reference
but not Array
, but most of the 10% is references anyway. I will try to benchmark memory usage of each option to see what is better.
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.
For context: here are the statistics of the r-a repo:
total_types: 656222 (100%)
tuples: 11492 (1.75%)
path_types: 552122 (84.14%)
raw_ptrs: 13167 (2.01%)
references: 58745 (8.95%)
references_with_lifetimes: 10529 (1.60%)
arrays: 2792 (0.43%)
slices: 6691 (1.02%)
fns: 821 (0.13%)
impl_traits: 1523 (0.23%)
dyn_traits: 4723 (0.72%)
macros: 1050 (0.16%)
This shows that if we are really concerned by the boxing, we can split Reference
into ReferenceWithLifetime
and ReferenceWithoutLifetime
and box only the latter, and then we'll have a bit more than 2% boxed.
@@ -1897,100 +1899,150 @@ pub fn write_visibility( | |||
} | |||
} | |||
|
|||
impl HirDisplay for TypeRef { | |||
pub trait HirDisplayWithTypesMap { |
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.
Can we just add &TypesMap
to the HirFormatter
, default that to the &'static EMPTY
and add a new method to HirDisplay
for hir_fmt_with_typemap
or so? Up to you if you think that would turn out "cleaner" or not, just throwing ideas
/// If this is set, that means we're in a context of a freshly expanded macro, and that means | ||
/// we should not use `TypeRefId` in diagnostics because the caller won't have the `TypesMap`, | ||
/// instead we need to put `TypeSource` from the source map. | ||
types_source_map: Option<&'a TypesSourceMap>, |
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.
I take it this is due to type source stuff being pre-expansion? Would be nice if we can figure out a way to make this work at some point
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.
Yes. This does work, but is a bit ugly. It means every diagnostic will have to be Either<TypeRefId, TypesSource>
. This isn't an incrementality problem because we touch the AST anyway when there are types macros, but it is not nice. OTOH I don't see a good way to avoid this, type refs are in the item tree so they cannot be post expansion, and making another lowering layer will be more expensive performance wise. And the situation isn't that terrible, it's just not nice.
// FIXME: That may be better served by mutating `self` then restoring, but this requires | ||
// making it `&mut self`. |
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.
Gotta say TyLoweringContext
is getting more and more cursed with all of this stuff and the interior mutability 😬
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.
Yeah I intend to clean that up in a follow-up PR, didn't wanted to do it now because it's already too big.
So that given a `TypeRef` we will be able to trace it back to source code. This is necessary to be able to provide diagnostics for lowering to chalk tys, since the input to that is `TypeRef`. This means that `TypeRef`s now have an identity, which means storing them in arena and not interning them, which is an unfortunate (but necessary) loss but also a pretty massive change. Luckily, because of the separation layer we have for IDE and HIR, this change never crosses the IDE boundary.
This saves back 15mb that went for typeref source maps.
This saves 16mb on `analysis-stats .`.
This saves 8mb.
Thanks to the observation (supported by counting) that the vast majority paths have neither generics no type anchors, and thanks to a new datastructure `ThinVecWithHeader` that is essentially `(T, Box<[U]>)` but with the size of a single pointer, we are able to reach this feat. This (together with `ThinVecWithHeader`) makes the possibility to shrink `TypeRef`, because most types are paths.
Only references and arrays need to be boxed, and they comprise only 9.4% of the types (according to counting on r-a's code). This saves 17mb.
ce82b01
to
bf7edd3
Compare
I'll merge this on monday just in case so we have time for fixing stuff before the next release |
So that given a
TypeRef
we will be able to trace it back to source code.This is necessary to be able to provide diagnostics for lowering to chalk tys, since the input to that is
TypeRef
. This is the first step towards diagnostics for ty lowering. The next steps will be smaller :PThis means that
TypeRef
s now have an identity, which means storing them in arena and not interning them, which is an unfortunate (but necessary) loss but also a pretty massive change. Luckily, because of the separation layer we have for IDE and HIR, this change never crosses the IDE boundary.This gives an unfortunate regression of ~170mb in
analysis-stats .
.Sorry for the size of the PR; I did some extra things but they are trivial - most of the code is necessary and impossible to split.