-
Notifications
You must be signed in to change notification settings - Fork 164
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
fixed-point iteration support #603
Conversation
✅ Deploy Preview for salsa-rs canceled.
|
CodSpeed Performance ReportMerging #603 will degrade performances by 6.58%Comparing Summary
Benchmarks breakdown
|
This is very cool! (Admittedly, I say this pre-review.) |
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.
This looks great. I left a few comments where I struggled understanding the implementation or had smaller suggestions.
In writing more comprehensive tests for this, I realized that it needs some changes to correctly handle multi-revision scenarios; taking it to Draft mode until I get that fixed. |
Ok, multiple-revision cases are now fixed, and we now populate the initial provisional value only lazily, in case a cycle is actually encountered, which should reduce the number of memos created by quite a lot. Also added a bunch of tests, including multiple-revision cases and one test involving durability. Still need to add cross-thread cycle tests. |
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.
The lazy creation of the initial value is a neat improvement. Nice for taking the time to work on it !
The benchmarks show a 4-5% regression. It seems that we're now resizing some hash maps more often. Are we reporting more tracked reads than before? Could you take a look what's causing it? |
Initial experiments using this in the red-knot type checker are promising: astral-sh/ruff#14029 Not yet using it for loopy control flow in that PR, but there are cycles in the core type definitions of Python builtins and standard library, which we previously had a hacky fallback in place for using Salsa's previous cycle fallback support. Moving over to fixpoint iteration just worked, and fixed the type of a builtin impacted by the cycle. On the downside, it is a performance regression. Need to do more work there. |
src/function/maybe_changed_after.rs
Outdated
/// Result of memo validation. | ||
pub enum VerifyResult { | ||
/// Memo has changed and needs to be recomputed. | ||
Changed, | ||
|
||
/// Memo remains valid. | ||
/// | ||
/// Database keys in the hashset represent cycle heads encountered in validation; don't mark | ||
/// memos verified until we've iterated the full cycle to ensure no inputs changed. | ||
Unchanged(FxHashSet<DatabaseKeyIndex>), | ||
} |
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.
This change (bool
being replaced by VerifyResult
) also seems like it will make non cycle queries pay a hefty perf price. That type has a destructor and is 40 bytes where as the previous return value could fit into a register, this can't.
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'll focus first on getting consistently right behavior (including across threads) and then revisit this and other possible ways to claw back perf. Off the top of my head I'm not sure how to improve this, will need to think about it.
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 of course, I am mainly noting down what I saw, no need to tackle perf nits/reviews until things are working properly. (also happy to help out with perf work on this PR once its at that stage)
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.
With the option boxing we've now significantly reduced the size of VerifyResult
, at least.
On a related perf note, would it make sense to move the dataflow test (or a maybe slightly more involved version) into a benchmark? |
@carljm -- to confirm, not yet ready for my review, right? (or did I forget something) |
@nikomatsakis I commented last week at https://salsa.zulipchat.com/#narrow/channel/333573-Contributing-to-Salsa/topic/fixed.20point.20cycles that this was ready for review. Two things have come up since:
I think a review from you of what's here could still be useful (you might have an idea where the race condition is that is causing the test failure?) but it's also fine if you want to wait until I sort the above items. |
I may try and see if https://github.com/awslabs/shuttle can track down the race condition. It's a bit invasive to use it, since it requires that all our uses from |
Ok. I will try to find time to review just in case. Im busy this week with Things but will do my best. |
Ok, problem (2) is addressed; I've merged in latest main branch and resolved all conflicts. Now back to problem (1). |
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.
This looks pretty good. I think we gotta get it landed and start testing it.
Oops, looks like we're hitting some GitHub CI job limits. |
No objections to the design or approach of this, but I was wondering if we could get rust-lang/rust-analyzer#18964 landed first. For the corresponding PR to land in rust-analyzer, we'll need an |
I'm not in a big rush to merge this on my side, though the merge conflicts do get exhausting to deal with. I'll add some more tests and some documentation, and work on using it in red-knot in the mean time. We may learn some new things from that. |
Yeah, I'm familiar with the rebase pains. Hopefully you won't have too many conflicts with the remaining, in-flight changes, but in the event you do, I want to set a tolerably short upper bound for my "don't land!" request. |
The current code in this branch requires Rust 1.83, for
Main branch doesn't compile with 1.76, it only compiles with 1.80 or newer. The tests only actually pass with 1.84 or newer, because we have snapshotted compiler output in the So we clearly don't have any CI set up to enforce our declared MSRV. I put up #712 to fix this. |
Ok, I added the necessary boilerplate here to maintain compatibility with Rust 1.80 for now. |
Stumbled upon this issue #193, might be good to check whether that is still relevant after this PR rewriting most of the cycle handling |
Another related issue #9 |
88c686d
to
5b44fbe
Compare
I added some more tests (for tracked-struct and accumulator output from queries in cycles, panics in cycle handling in a cross-thread cycle), and fixed a bug in I also updated the book sections on cycle handling. I'm afraid I didn't reproduce the detail of the previous "plumbing" section on the old cycle handling, but I did add a doc comment about the new cycle handling in I now consider this branch "done" and ready for merge, pending any further review comments (cc @nikomatsakis, @MichaReiser, @Veykril). I'll wait to merge it until @davidbarsky and @Veykril give the green light from the rust-analyzer side. |
Feel free to land this! We—rust-analyzer—got the changes we needed in https://github.com/salsa-rs/salsa/releases/tag/salsa-v0.19.0 and are happily giving up the "land stuff" lock :) |
Squashed to a single commit in preparation for landing; the commit history here has a lot of junk and false starts, as well as many merges from main branch. |
This PR removes the existing unwind-based cycle fallback support (a plus for WASM compatibility), and replaces it with support for fixpoint iteration of cycles.
To opt in to fixpoint iteration, provide two additional arguments to
salsa::tracked
on the definition of a tracked function:cycle_initial
andcycle_fn
. The former is a function which should provide a provisional starting value for fixpoint iteration on this query, and the latter is a function which has the opportunity, after each iteration that failed to converge, to decide whether to continue iterating or fallback to some fixed value. See the added tests and docs for details.Usability points that should be covered in the documentation:
cycle_fn
andcycle_initial
on every query that might end up as the "head" of a cycle (that is, queried for its value while it is already executing.)cycle_fn
andcycle_initial
so as to cause iteration to diverge and never terminate; it's up to the user to avoid this. Techniques to avoid this include a) ensuring that cycles will converge, by defining the initial value and the queries themselves monotonically (for example, in a type-inference scenario, the initial value is the bottom, or empty, type, and types will only widen, never narrow, as the cycle iterates -- thus the cycle must eventually converge to the top type, if nowhere else), and/or b) with a larger hammer, by ensuring thatcycle_fn
respects the iteration count it is given, and always halts iteration with a fallback value if the count reaches some "too large" value.cycle_fn
andcycle_initial
such that memoized results can vary depending only on the order in which queries occur. Avoid this by minimizing the number of tracked functions that support fixpoint iteration and ensuring initial values and fallback values are consistent among tracked functions that may occur in a cycle together.cycle_fn
andcycle_initial
queries, but if the query you call re-enters the same cycle, it could lead to unexpected behavior. Take care what queries you call inside cycle recovery functions.