Simple LRU garbage collection for interned values#839
Simple LRU garbage collection for interned values#839MichaReiser merged 7 commits intosalsa-rs:masterfrom
Conversation
✅ Deploy Preview for salsa-rs canceled.
|
CodSpeed Performance ReportMerging #839 will not alter performanceComparing Summary
|
b500f7e to
b54e365
Compare
Interesting. I suspect this is new but I'm investigating a similar issue related to interned in cycles |
|
That makes me wonder if your PR might help to come up with simpler repros for the fixpoint issues that we're seeing. I should give this a try tomorrow. On the perf side. You could try using the |
|
I quickly tried the branch in Red Knot but I also got new panics that we didn't see before. That makes me suspect that some revision tracking might be off in this PR? What I like to do to debug those issues is to compare the tracing output and see where things go different |
|
I think the issue is unrelated to durabilities but about interned query arguments. If a query takes an interned struct as an argument then an older memo may be incorrectly verified after the slot has been reused, but the IDs still match. I think we have to check that the memo and first_interned_at revisions match to avoid this, but I'm not exactly sure where that would be. |
|
I think I understand. What you're saying is that I think will need something similar to what we have in tracked structs where salsa informs other ingredients that the memo was collected so that they can free resources as well if necessary: The second part in tracked struct that protects against this is that we separately track |
073a988 to
7f89c28
Compare
3ac564d to
a232751
Compare
|
I updated the PR to add read dependencies and clear memos on reuse (I believe we will need to defer reclamation of the memos), but there are a still some ty tests failing that I'm unsure about. |
39c219b to
bcb023c
Compare
e366c0e to
5e693d9
Compare
|
Hmm I'm not sure we can remove the read dependency on creation with generational IDs, we still need |
No, I don't think we can remove it from the writing query. We need the dependency to ensure the interned value gets re-created when it was claimed previously |
8fc9557 to
54b6bba
Compare
|
I updated the PR description with the new changes, this should be ready for review. |
I believe you mean high durability here? It would be helpful to add some details to why
I believe we originally discussed a GC where interned values would move from green, to yellow, to red generations. I assume what you implemented now isn't that. Can you give some context to why?
Is this after removing the |
MichaReiser
left a comment
There was a problem hiding this comment.
I left a few questions, but this overall looks great!
The reason is that it doesn't really make sense to have a specific capacity for the LRU. Instead, we care more about reusing values that are unlikely to be accessed again. Bounding the LRU means we might end up evicting values that were read in a recent revision. It also makes it a lot more expensive to grow the LRU and shuffle around the values based on their color, especially for a cold run. The LRU implemented in this PR does have some idea of coloring based on revision, i.e. values that have already been read in the current revision are not moved in the LRU, which as I understand is the main benefit of the coloring idea.
Yes, sorry. I believe issue was that we need to make sure that |
|
I removed the |
|
Would it be possible to release med/high durability revisions if we detect a med/high durability change? I guess the problem remains that med/high durability in most cases would need to be skipped, which could outweight the benefits of reusing them in the less common case of a medium/high durability change. |
MichaReiser
left a comment
There was a problem hiding this comment.
This is great. Thank you.
I think it would be great if we could avoid adding high and medium durability interned values until we have support for collecting them.
@Veykril @davidbarsky any thoughts from your side on the design?
| let value_shared = unsafe { &mut *value.shared.get() }; | ||
|
|
||
| // The slot was reused. | ||
| if value_shared.id.generation() > input.generation() { |
There was a problem hiding this comment.
Do we need this check, given that the id changes when the generation changes?
(which in turn means that no existing query can depend on the now "new" interned value)
There was a problem hiding this comment.
I think this check is still needed, because it allows queries that depend on the old value to see that the slot was reused. There is a test in ty that fails if I remove it.
src/interned.rs
Outdated
| } | ||
|
|
||
| id | ||
| // We can't collect higher durability values until we have a mutable reference to the database. |
There was a problem hiding this comment.
Why is that? (I did not read up on the discussions on the PR) Would be good to add that to the comment?
| /// inputs changes, then the creator query may create this struct | ||
| /// with different values. | ||
| durability: AtomicU8, | ||
| durability: Durability, |
There was a problem hiding this comment.
Note that this field wastes 8 bytes while only occupying a single byte due to padding and alignment of ValueShared
Basic implementation of #597.
The garbage collection strategy used here is very simple.
&mut db. This could be supported with the above.This is by no means a perfect strategy, but we can always tune it as needed once the infrastructure is in place. We may also want to expose the
Nactive revisions constant as a configuration option in the#[interned]macro (it seems somewhat related to durability).The LRU list is sharded by hash, very similar to dashmap. There seems to be no performance difference from the previous implementation in multi-threaded
tybenchmarks (though there was a significant regression without sharding). There is also no significant performance difference in single-threaded benchmarks thanks to #864, which lets us avoid additional read dependencies.