Skip to content
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

fix(resolver): share conflict cache between activation retries #14692

Merged
merged 1 commit into from
Oct 15, 2024

Conversation

x-hgg-x
Copy link
Contributor

@x-hgg-x x-hgg-x commented Oct 15, 2024

What does this PR try to resolve?

Found in Eh2406/pubgrub-crates-benchmark#6.

Currently the resolve is done in a loop, restarting if there are pending dependencies which are not yet fetched, and recreating the conflict cache at each iteration.

This means we do a lot of duplicate work, especially if the newly fetched dependencies has zero or few dependencies which doesn't conflict.

This also means that the maximum depth of the dependency graph has a big impact on the total resolving time, since adding a dependency not yet seen means we will restart the resolve at the next iteration.

Here is the output of the resolve for the master branch using solana-core = "=1.2.18" in Cargo.toml, printing the duration from the start after each iteration:

    Updating crates.io index
103.142341ms   [=====>                           ] 1 complete; 0 pending
118.486144ms   [========>                        ] 2 complete; 0 pending
785.389055ms   [===========>                     ] 62 complete; 0 pending
4.916033377s   [==============>                  ] 277 complete; 0 pending
9.13101404s    [=================>               ] 439 complete; 0 pending
50.083251549s  [====================>            ] 520 complete; 0 pending
133.401303265s [=======================>         ] 561 complete; 0 pending
214.120483345s [==========================>      ] 565 complete; 0 pending
294.180677785s [=============================>   ] 566 complete; 0 pending
    Locking 536 packages to latest compatible versions

To improve the situation, this PR moves the conflict cache outside the activation loop so it can be reused for all iterations.

This is possible since when using an online registry, dependencies which are not yet fetched are not added to the candidate summary:

let mut deps = deps
.into_iter()
.filter_map(|(dep, features)| match self.query(&dep, first_version) {
Poll::Ready(Ok(candidates)) => Some(Ok((dep, candidates, features))),
Poll::Pending => {
all_ready = false;
// we can ignore Pending deps, resolve will be repeatedly called
// until there are none to ignore
None
}
Poll::Ready(Err(e)) => Some(Err(e).with_context(|| {
format!(
"failed to get `{}` as a dependency of {}",
dep.package_name(),
describe_path_in_context(cx, &candidate.package_id()),
)
})),
})
.collect::<CargoResult<Vec<DepInfo>>>()?;

On the other hand, if a dependency query returns Poll::Ready, then all compatible summaries are returned, so we cannot have a partial view where only some compatible summaries would be returned. This means we cannot add a conflict in the cache which would be incompatible with future fetches, and therefore the conflict cache can be shared.

Here is the output of the resolve with this PR:

    Updating crates.io index
98.239126ms   [=====>                           ] 1 complete; 0 pending
127.528982ms  [========>                        ] 2 complete; 0 pending
821.601257ms  [===========>                     ] 62 complete; 0 pending
4.67917132s   [==============>                  ] 277 complete; 0 pending
5.933230172s  [=================>               ] 431 complete; 0 pending
14.74321904s  [====================>            ] 508 complete; 0 pending
24.607428893s [=======================>         ] 546 complete; 0 pending
24.700610469s [==========================>      ] 550 complete; 0 pending
24.757651875s [=============================>   ] 551 complete; 0 pending
    Locking 536 packages to latest compatible versions

Besides the huge performance savings between iterations, sharing the conflict cache has the side effect of eliminating dead paths earlier in the resolve after a restart. We can see that the duration of all iterations using this PR is less than the duration of the last iteration on master, and that it needs to resolve less dependencies overall.

@rustbot
Copy link
Collaborator

rustbot commented Oct 15, 2024

r? @ehuss

rustbot has assigned @ehuss.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added A-dependency-resolution Area: dependency resolution and the resolver S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Oct 15, 2024
@x-hgg-x
Copy link
Contributor Author

x-hgg-x commented Oct 15, 2024

r? Eh2406

@rustbot rustbot assigned Eh2406 and unassigned ehuss Oct 15, 2024
@epage
Copy link
Contributor

epage commented Oct 15, 2024

To double check my understanding, the slowdown is only relevant if you don't have a lockfile because wee prefetch deps in a lockfile?

@x-hgg-x
Copy link
Contributor Author

x-hgg-x commented Oct 15, 2024

To double check my understanding, the slowdown is only relevant if you don't have a lockfile because wee prefetch deps in a lockfile?

Yes, when a lockfile is present the resolve takes only a few milliseconds.

@Eh2406
Copy link
Contributor

Eh2406 commented Oct 15, 2024

This is a delightfully small diff. The argument for correctness is kind of suttle. But now that we have talked it over N times, I am convinced it is correct.

@bors r+

@bors
Copy link
Contributor

bors commented Oct 15, 2024

📌 Commit 46ea11a has been approved by Eh2406

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Oct 15, 2024
@bors
Copy link
Contributor

bors commented Oct 15, 2024

⌛ Testing commit 46ea11a with merge 430fabe...

@bors
Copy link
Contributor

bors commented Oct 15, 2024

☀️ Test successful - checks-actions
Approved by: Eh2406
Pushing 430fabe to master...

@bors bors merged commit 430fabe into rust-lang:master Oct 15, 2024
22 checks passed
@x-hgg-x x-hgg-x deleted the resolver-perf-5 branch October 15, 2024 15:32
bors added a commit to rust-lang-ci/rust that referenced this pull request Oct 15, 2024
Update cargo

14 commits in 15fbd2f607d4defc87053b8b76bf5038f2483cf4..8c30ce53688e25f7e9d860b33cc914fb2957ca9a
2024-10-08 21:08:11 +0000 to 2024-10-15 16:43:16 +0000
- docs: More information on what is and isn't included by cargo package (rust-lang/cargo#14684)
- fix(resolver): share conflict cache between activation retries (rust-lang/cargo#14692)
- fix(git): dont fetch tags by default (rust-lang/cargo#14688)
- Support package selection options like `--exclude` in `cargo publish` (rust-lang/cargo#14659)
- docs: install options -&gt; uninstall options (rust-lang/cargo#14682)
- docs: tools should only interpret a line starting with `{` as JSON (rust-lang/cargo#14677)
- cargo test --help: clarify --tests and --benches (rust-lang/cargo#14675)
- docs(env): minor improvements in environment variables doc (rust-lang/cargo#14676)
- docs: document official external commands (rust-lang/cargo#14669)
- Fix panic when running cargo tree on a package with a cross compiled bindep (rust-lang/cargo#14593)
- Remove the support for `Cargo.toml` of the cargo-script (rust-lang/cargo#14670)
- docs(resolver): Lay groundwork for documenting MSRV-aware logic (rust-lang/cargo#14662)
- chore(deps): update rust crate pulldown-cmark to 0.12.0 (rust-lang/cargo#14668)
- Improve resolver speed (rust-lang/cargo#14663)
@rustbot rustbot added this to the 1.84.0 milestone Oct 16, 2024
github-actions bot pushed a commit to rust-lang/miri that referenced this pull request Oct 17, 2024
Update cargo

14 commits in 15fbd2f607d4defc87053b8b76bf5038f2483cf4..8c30ce53688e25f7e9d860b33cc914fb2957ca9a
2024-10-08 21:08:11 +0000 to 2024-10-15 16:43:16 +0000
- docs: More information on what is and isn't included by cargo package (rust-lang/cargo#14684)
- fix(resolver): share conflict cache between activation retries (rust-lang/cargo#14692)
- fix(git): dont fetch tags by default (rust-lang/cargo#14688)
- Support package selection options like `--exclude` in `cargo publish` (rust-lang/cargo#14659)
- docs: install options -&gt; uninstall options (rust-lang/cargo#14682)
- docs: tools should only interpret a line starting with `{` as JSON (rust-lang/cargo#14677)
- cargo test --help: clarify --tests and --benches (rust-lang/cargo#14675)
- docs(env): minor improvements in environment variables doc (rust-lang/cargo#14676)
- docs: document official external commands (rust-lang/cargo#14669)
- Fix panic when running cargo tree on a package with a cross compiled bindep (rust-lang/cargo#14593)
- Remove the support for `Cargo.toml` of the cargo-script (rust-lang/cargo#14670)
- docs(resolver): Lay groundwork for documenting MSRV-aware logic (rust-lang/cargo#14662)
- chore(deps): update rust crate pulldown-cmark to 0.12.0 (rust-lang/cargo#14668)
- Improve resolver speed (rust-lang/cargo#14663)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-dependency-resolution Area: dependency resolution and the resolver S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants