Use generational identifiers for tracked structs#864
Use generational identifiers for tracked structs#864MichaReiser merged 5 commits intosalsa-rs:masterfrom
Conversation
✅ Deploy Preview for salsa-rs canceled.
|
CodSpeed Performance ReportMerging #864 will degrade performances by 15.82%Comparing Summary
Benchmarks breakdown
|
This is huge! I'm leaning towards removing the |
57ec532 to
66bbb88
Compare
davidbarsky
left a comment
There was a problem hiding this comment.
lgtm, merge whenever you'd like to?
|
I'd be interested to explore the memory overhead if we changed |
d980d6c to
5ce6735
Compare
| "salsa_event(DidValidateMemoizedValue { database_key: read_value(Id(400)) })", | ||
| "salsa_event(DidReinternValue { key: query_d::interned_arguments(Id(800)), revision: R2 })", | ||
| "salsa_event(DidValidateMemoizedValue { database_key: query_d(Id(800)) })", | ||
| "salsa_event(DidValidateMemoizedValue { database_key: read_value(Id(401)) })", |
There was a problem hiding this comment.
Huh, this is interesting
There was a problem hiding this comment.
Do you know why this test changed?
There was a problem hiding this comment.
I'm not sure. Maybe the memos are being validated instead of the function being re-executing, if the function previously had a read dependency on a tracked struct (and that dependency no longer exists)?
|
I updated the PR to store Unfortunately it looks like the benchmarks don't like this change... |
5ce6735 to
4920a8a
Compare
|
Is there any perf difference on ty? (When running on codspeed) |
4920a8a to
19d00eb
Compare
|
No performance difference on ty benchmarks (astral-sh/ruff#18226). |
|
I plan to review this tomorrow. I'm okay with the regression given that it enables interned GC without a 50% incremental perf and memory regression. If you haven't done so already, maybe take an hour or two to see if you can spot the source of the regression in a local recorded profile (take the benchmark that regresses the most) This is a substantial change where I'd like to get at least a thumbs up from r-a too, given that the performance is now regressing on micro benchmarks. Cc @Veykril |
I don't think there is a specific source, I think the regression is directly related to the size of |
19d00eb to
8b6cc9f
Compare
| let updated_id = self.update(zalsa, current_revision, id, ¤t_deps, fields); | ||
| if id != updated_id { | ||
| // Overwrite the previous ID if we are reusing the slot with new fields. | ||
| zalsa_local.store_tracked_struct_id(identity, updated_id); |
There was a problem hiding this comment.
This feels a little wrong, but I think it is correct. If the struct is recreated with the same fields, we want to return the latest generation for the tracked structs to align with the memos. If it is recreated with new fields, we have to create a new generation and invalidate the previous version.
MichaReiser
left a comment
There was a problem hiding this comment.
This is awesome. I've a few smaller comments that should be addressed before landing (assuming r-a is on board with this change)
| "salsa_event(DidValidateMemoizedValue { database_key: read_value(Id(400)) })", | ||
| "salsa_event(DidReinternValue { key: query_d::interned_arguments(Id(800)), revision: R2 })", | ||
| "salsa_event(DidValidateMemoizedValue { database_key: query_d(Id(800)) })", | ||
| "salsa_event(DidValidateMemoizedValue { database_key: read_value(Id(401)) })", |
There was a problem hiding this comment.
Do you know why this test changed?
|
I would like to cut another release before merging this if possible. I expect this to have a noticable memory impact for rust-analyzer, having this be part of a separate release cycle would make that easier to check. |
6fbf77d to
9c27c3d
Compare
9c27c3d to
bc403d9
Compare
| // the unlikely case that the ID is already at its maximum generation, we are forced to leak | ||
| // the previous slot and allocate a new value. | ||
| if id.generation() == u32::MAX { | ||
| return Err(()); |
There was a problem hiding this comment.
I think it would be good to log at least an info message that we leaked this node.
|
When rebased onto this branch, #839 is actually a ~5% improvement to ty's incremental performance. |
|
@Veykril what's your timeline on the next release? |
|
I'm fine with cutting a release now if you want to land this to try it out in ruff/ty asap |
|
I'll land this once all feedback is addressed. |
3a47df9 to
5a2c276
Compare
Pack a generation into input IDs.
The generation of a tracked struct is incremented after it is reused, allowing us to avoid read dependencies. Generational IDs were originally meant for #839, as adding the necessary read dependency on interned structs that may be reused introduced a large (~50%) regression to ty's incremental performance.
This increases the size of
Idfrom au32to au64. However, if the generation is restricted tou16, and ingredient indices are restricted tou16, this does not increase the size ofDatabaseKeyIndex, so the memory usage effect is limited (~5% increase to ty's peak memory usage). However, this should allow us to implement garbage collection for interned values without significant performance concerns, so memory usage over time should benefit.If the generation exceeds
u16::MAX, we can fallback to adding read dependencies on tracked structs. An alternative would be to leak the slot, which would also allow us to remove thecreated_atfield on tracked structs and may alleviate the memory usage concerns. This might be more feasible if the generation stole a few more bits from ingredient indices (as the number of ingredients is effectively static for a given salsa program).This has a small (~4%) performance improvement on ty's benchmarks.