Resolver: Parallelize the import resolution loop#158845
Resolver: Parallelize the import resolution loop#158845LorrensP-2158466 wants to merge 4 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.
…henkov Resolve: more preperation work for parallelizing the import resolution loop This is basically rust-lang/rust#158845 but we do not: - actually use `par_slice` because: - we do not migrate `CmRefCell` to use `RwLocks` (yet) because of perf reasons. The resolution loop is now in place instead of recollecting indeterminate imports. r? @petrochenkov
This comment has been minimized.
This comment has been minimized.
|
Finished benchmarking commit (7af71a9): comparison URL. Overall result: ❌ regressions - please read:Benchmarking means the PR may be perf-sensitive. It's automatically marked not fit for rolling up. Overriding is possible but disadvised: it risks changing compiler perf. Next, please: If you can, justify the regressions found in this try perf run in writing along with @bors rollup=never rustc-perf Instruction countOur most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.
Max RSS (memory usage)Results (secondary 4.2%)A less reliable metric. May be of interest, but not used to determine the overall result above.
CyclesResults (primary 1.6%, secondary 2.7%)A less reliable metric. May be of interest, but not used to determine the overall result above.
Binary sizeThis perf run didn't have relevant results for this metric. Bootstrap: 489.502s -> 487.276s (-0.45%) |
|
@LorrensP-2158466 |
…ot::RwLock`. Implementation uses the same technique as with `Lock` by using a C enum together with a union around the locking primitives.
… associated function
|
@petrochenkov cherry picked the 3 commits. |
|
@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.
|
Finished benchmarking commit (b4cc008): comparison URL. Overall result: ❌✅ regressions and improvements - please read:Benchmarking means the PR may be perf-sensitive. It's automatically marked not fit for rolling up. Overriding is possible but disadvised: it risks changing compiler perf. Next, please: If you can, justify the regressions found in this try perf run in writing along with @bors rollup=never rustc-perf Instruction countOur most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.
Max RSS (memory usage)Results (primary 2.2%, secondary 3.0%)A less reliable metric. May be of interest, but not used to determine the overall result above.
CyclesResults (primary 3.6%, secondary -4.5%)A less reliable metric. May be of interest, but not used to determine the overall result above.
Binary sizeThis perf run didn't have relevant results for this metric. Bootstrap: 487.635s -> 488.963s (0.27%) |
|
Nice, the conditional |
|
Wait, why does |
I think I already explained why we need this, though I can't find. Basically, in the functions concerned with retrieving a resolution, we use fn resolutions(&self, module: Module<'ra>) -> &'ra Resolutions<'ra> {
if !module.is_local() {
// as long as 1 thread is building this external table, all other threads will wait
module.populate_on_access.call_once(|| {
*module.lazy_resolutions.borrow_mut_unchecked() =
self.build_reduced_graph_external(module.expect_extern());
});
}
&module.0.0.lazy_resolutions
}
fn resolution(
&self,
module: Module<'ra>,
key: BindingKey,
) -> Option<ReadGuard<'ra, NameResolution<'ra>>> {
self.resolutions(module).borrow().get(&key).map(|resolution| resolution.0.borrow())
}These get called during speculative resolution, so the borrow counter needs to be synchronized. We use type Resolutions<'ra> = CmRefCell<FxIndexMap<BindingKey, NameResolutionRef<'ra>>>;
pub(crate) type NameResolutionRef<'ra> = Interned<'ra, CmRefCell<NameResolution<'ra>>>; |
…henkov Resolve: more preperation work for parallelizing the import resolution loop This is basically rust-lang/rust#158845 but we do not: - actually use `par_slice` because: - we do not migrate `CmRefCell` to use `RwLocks` (yet) because of perf reasons. The resolution loop is now in place instead of recollecting indeterminate imports. r? @petrochenkov
|
I see, it is due to the remaining Could you also extract the structures from |
Yeah, but we still need interior mutability for those 2 structures during speculative resolution, not to mention the other places it is used for // Not all fields, only relevant.
struct ModuleData<'ra> {
/// Mapping between names and their (possibly in-progress) resolutions in this module.
/// Resolutions in modules from other crates are not populated until accessed.
lazy_resolutions: Resolutions<'ra>,
/// Macro invocations that can expand into items in this module.
unexpanded_invocations: CmRefCell<FxHashSet<LocalExpnId>>,
glob_importers: CmRefCell<Vec<Import<'ra>>>,
globs: CmRefCell<Vec<Import<'ra>>>,
/// Used to memoize the traits in this module for faster searches through all traits in scope.
traits: CmRefCell<
Option<Box<[(Symbol, Decl<'ra>, Option<Module<'ra>>, bool /* lint ambiguous */)]>>,
>,
}So we need some structure that only allows reads during speculative resolution, and somewhow switches to interior mutability when not in speculative resolution. I think for some of the fields, something like a |
If something needs mutability during speculative resolution, it doesn't use |
|
I am sorry, I worded it wrong:
I meant that we need read access during speculative resolution and interior mutability elsewhere for things wrapped in
So:
While this is true, that is thus only half of it. So we need to find some very smart datastructure that can be synchronized or not depending the use of it. Which is very unsafe |
Yes, but it doesn't mean the data in |
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