Resolver: Parallelize the import resolution loop - #158845
Resolver: Parallelize the import resolution loop#158845LorrensP-2158466 wants to merge 2 commits into
Conversation
|
Lets see what CI says. I have tried to add comments to changes to show my thought process behind them, but i'll make another pass here as well to be sure. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
17bd1cb to
9aa1069
Compare
This comment has been minimized.
This comment has been minimized.
9aa1069 to
a45ba86
Compare
|
@bors try @rust-timer queue |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…r=<try> Resolver: Parallelize the import resolution loop
This comment has been minimized.
This comment has been minimized.
Same as #158845 (comment), I cannot answer without repeating the work and trying to implement in myself, but the high-level design seems plausible. |
|
I've finally read the safety comments in detail, they look more convincing now. |
I'll think that I will extend them a bit and add a comment to the |
|
Updated the comments and used |
|
Benchmarking the changes in #160064. |
|
I'm also tempted to add extra methods for |
|
https://github.com/LorrensP-2158466/ref_mut/tree/main has the code for the miri tests, as you will see (and the comments in this pr explain) |
|
decided to create a |
This comment has been minimized.
This comment has been minimized.
At least right now it probably doesn't need to be atomic though. |
Again, we'll need to benchmark whether eliding this boolean assert even matters, considering that it's not even atomic. |
|
I'll split the |
Yes, after the |
This comment has been minimized.
This comment has been minimized.
|
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
… parallel loop for import resolution. + impl `DynSend`/`DynSync` for all relevant data structures + safety comments.
…ot::RwLock`. Implementation uses the same technique as with `Lock` by using a C enum together with a union around the locking primitives.
|
Rebased to fix conflicts and cleanup the PR, it now contains:
@rustbot ready. |
I don't think we'll merge this part, given the unconvincing perf results from the linked PR and the amount of complexity/unsafety. |
| // SAFETY: a shared `RefOrMut` only ever yields `&T`, so letting multiple threads | ||
| // read through it concurrently is just ordinary shared-reference aliasing; no | ||
| // thread can obtain `&mut T` through it, so there is no data race to worry about. | ||
| unsafe impl<'a, T: DynSync> DynSync for RefOrMut<'a, T> {} |
There was a problem hiding this comment.
This impl is unnecessary and can be removed, I think we discussed this somewhere above.
| scope = next.get(); | ||
| macro_rules_scope.set(scope); | ||
| scope = *next.borrow(); | ||
| *macro_rules_scope.borrow_mut() = scope; |
There was a problem hiding this comment.
I'm not sure this is algorithmically correct.
We may need to protect a larger piece of logic by a lock, maybe the whole loop.
I need to think (but I'm busy this and next week).
There was a problem hiding this comment.
I am not entirely familiar with the chained scopes of macro_rules!, but I can assume there are no cycles, so this should be correct.
Example chain:
A -> B -> C -> D -> E
If multiple threads try to compress the path A -> E starting at A, then no matter the ordering, they will always end up at E.
t1reads nodeAand writes on that nodeB, thent2will readB.t1andt2read nodeA, both will then writeB, which is the result we expect.
I see it as a recursive property as well, so it will hold until both threads read E in their last step.
If multiple threads are compressing on the same path, their results will be the same as well. For example: t1 compresses A -> E and t2 will compress C -> E.
Since both only overwrite their respective nodes, A for t1 and C for t2, they can never interfere with each other. It doesn't matter what t2 is doing on its path, because for t1 the full path still "exists", if t2 managed to overwrite C before t1 gets to it, it just does one step less.
| } | ||
| }; | ||
| flag_decl.set((PendingDecl::Ready(decl), finalize || finalized, is_open)); | ||
| *flag_decl.write() = (PendingDecl::Ready(decl), finalize || finalized, is_open); |
There was a problem hiding this comment.
I'm not sure this is algorithmically correct either.
This case is similar to extern module loading and we may need to protect the whole loading process by a single lock.
There was a problem hiding this comment.
Yeah, this needs take a lock for the entire process. Depening on wether the extern crate is resolved do we do work or not.
We should then probably use a Mutex instead of a RwLock.
There was a problem hiding this comment.
At this point the Cache(Ref)Cell aliases can be removed and RwLocks and Mutexes can be used directly.
View all comments
Follow up of #159440. This pr implements the parallel part of
par_for_each_slice. And implementsDynSendandDynSyncforRefOrMutandCmCellto make the call topar_for_each_slicecompile.This is the bare minimum to make the parallel loop work and is not at all optimized, this will follow :).
r? @petrochenkov