-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Drop the im-rc dependency #9878
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @ehuss (or someone else) soon. Please see the contribution instructions for more information. |
@Eh2406 or @alexcrichton would you be able to review this? I was not involved in the original decision. Regarding benchmarking, |
To start with thank you for working on this! I have been watching the abandonment of From the introductory PR you linked, my reading is the main advantage of
Cargo test are probably going to mostly be benchmarking file IO, and only use the resolver in small ways. resolver-tests may be a little better, but it is also synthetic and not deterministic. Eric suggestion: Some of the type substitutions will probably need to be Sorry I ran out of time for a longer review. |
Thanks for the initial review!
Yeah, running initial benchmarks with
I can try to follow up with that, but after some initial looks into the git history of the affected file, it appears to not be as simple as reverting the PR that switched from |
That seems like a reasonable set of steps. You don't need to remove A recent discussion came up with the following as some good real-world projects (counts of dependencies given):
|
Alright, I've compiled cargo using im-rc into
new version slightly faster.
both versions have almost identical performance
significantly faster with im-rc
So the performance with my changes does not seem to correlate directly with the size of the dependency tree, otherwise the benchmarks for rustc should have been slower than those for cargo, but it performed best for rustc. I'll try using a |
Alright, I've switched the BTreeSet back to a BinaryHeap, performance seems to be slightly better, bit still a bit worse than with
slightly faster than with im-rc (!)
both versions have almost identical performance, within margin of error
was faster by ~40% with im-rc
both versions have almost identical performance, within margin of error
I also benchmarked this against some small-to-medium-sized projects and performance |
If you are having fun with this kind of perf work, I have two follow up questions.
If you are not having fun I understand. |
With
still very similar or slightly faster without im-rc
slightly slower without im-rc, but within margin of error
still ~40% slower without im-rc
Looks like those aren't much affected by this feature at all, performance numbers are almost identical whether the feature is enabled or not. The only exception is servo, where enabling this feature makes Here's some flamegraphs I generated from running my "old" and "new" versions of
It's a bit hard to read because PS: I'm starting to feel like I've had enough benchmarking fun for now ;) |
I don't have a ton to add over what @Eh2406 has already mentioned, but I'll reiterate that these types were used specifically for quick cloning capabilities. As seen on some of the crate graphs this can have a significant impact on Cargo's runtime. Also, though, I don't think that One option for some of these usages is to use |
Well ... if there's no meaningful way to benchmark my changes, then what can I do to move this forward? True, this change makes runtime a few fractions of a second worse (still below one second though) for some cargo commands in projects with very very large dependency trees. Is that really bad enough that you want to continue depending on a seemingly-abandoned crate that has confirmed correctness and panic! safety issues in the exact data types that are in use by cargo? PS: I've now also looked more closely into the |
Sorry I don't mean to say there's no meaningful way to benchmark your changes. My point is that these benchmarks, as-is, may not be benchmarking the most critical item which is resolution with a lock file already in place. Additionally, yes, the few fractions of a second here matter quite a lot. Cargo needs to get out of the way extremely quickly for incremental rebuilds, even of larger projects. Incremental rebuilds aren't always going to involve tons of rustc invocations that are super slow. For example if you Yes it's not great to use unmaintained code, but a problem with a library does not guarantee that Cargo's usage is problematic. There may still be a problem but mere existence isn't enough to convince me to rip it out immediately without further investigation for how it affects Cargo. |
I agree, for "CLI interactivity", a few hundred milliseconds can make a pretty big difference. I just wanted to say that for most projects I tested, the runtime differences were within the margin of error (rustc, cargo, servo, and a few other, smaller projects), but one project (paritytech/substrate) was a bit of an outlier, because with my changes, But as you noted, those were not really good targets for benchmarking. So could you give me a better, concrete example of a cargo command (or series of commands) that I could target for benchmarking / profiling my changes? Then, I can try to find performance bottlenecks, and if I find any, I could try to fix those on top of the current patch, and see if I can't improve runtime behaviour such that getting rid of im-rc wouldn't cause performance regressions. |
Looks to me like it is in
I wonder if we can call
Using
That is very good to know. Thank you for looking into it! |
I have been keeping an eye as new issues come in to the Edit: having gone back to review bodil/im-rs#124 is not so different from our use case. |
In the meantime, I've gone ahead and implemented the suggestion to wrap
In fact, time spent inside |
I listed two above:
Additionally since you're editing Cargo anyway you can add something like an early To reiterate, though, without concrete evidence pointing out that Cargo's usage of im-rc is indeed flawed as-is today, I am not personally going to be convinced to merge changes to switch to something else so long as it has a regression. I'm particularly worried about big projects, precisely the case that may be regressing here in the case of substrate. |
Ok, I re-ran benchmarks with those two commands.
With all crates I tested, this was actually slightly faster with my changes.
This command was also slightly faster with my changes, for all tested crates.
I understand that you're wary of merging changes like this one where the benefit might be small and there might be performance regressions for some projects. Even if this PR doesn't end up getting merged, it can serve as research for anybody who will be looking into replacing |
That's great news! That's definitely one of the biggest things to make sure doesn't regress, but it's also not necessarily everything. Do you plan on continuing to benchmark and evaluate this change, or are you waiting for a decision from someone else about whether this change is acceptable? |
I can continue evaluating this change, if there's more things to evaluate. But sure, it would be good to know if this PR (or something like it) might eventually be acceptable for merging, or if continuing to work on it will just be keeping me from doing more fruitful things with my time. |
I don't think anyone will have a problem with the idea behind this change, which is to maintain perf and drop an unmaintained dependency. The tricky part at this point is evaulating this. Are you willing to evaluate this? That would involve trying to stress the resolver in interesting ways. If you instead are waiting for us to tell you how to stress the resolver and benchmark this I would like to know. |
Willing - to some extent, yes. However, since I assume I am the one person in this thread who knows least about how the cargo resolver works and how to stress it in different ways, I would need some pointers about what to do next. |
Ok so just to make sure I put this into context, you're changing the data structure representations in Cargo in a core fashion here which is highly likely to have an impact on performance, something we care much about. At the same time, though, you're not really providing a ton of leadership from your end. I don't mean to dilute that which you've already done but so far it's not far above the minimum where things compile, tests pass, and you're running benchmarks we tell you to run. Conversely, though, I would expect that changes to a core part of a project are accompanied with an equivalent amount of understanding and willingness to investigate and explore the ramifications of the changes that are being made. You don't sound too willilng to do this and are happy to do what we tell you but not much else. This places a lot of work on the maintainers of Cargo because we basically end up doing the change ourselves vicariously through you, which is both inefficient and draining on us. It would be much more helpful, if you'd like to see this change to completion, if you could put in some time and effort to analyze the resolver and determine the performance impact of this. This frees us, the Cargo team, from doing a large portion of the work of determining how to evaluate this change. We of course can offer assistance and try to find blind spots, but ideally this is a collaborative process that doesn't involve the Cargo team doing everything in terms of evaluation. For example some concrete things I would expect to be evaluated is the performance on graphs that may not have lock files. It seems like there's some good data here to show that with a lock file performance is not regressing, but we still also don't want Cargo to take seconds-to-minutes to complete if there isn't a lock file. The This change by no means needs to be performance-neutral-or-better across the board. My goal is to understand the performance impact this has. The documentation of the structures here specifically say they get cloned a lot and performance is important, and with this PR those comments are basically stale and ignored. We unfortunately didn't do a great job of recording benchmarks in the past, which isn't great because it offloads work to this PR, but I don't think that it's necessarily an excuse to eschew things. As a final note, when you post benchmarks, it'd be great if mostly just a summary was posted. Reading over dozens of outputs of |
While this is not exactly the response I was hoping for, I understand that time is one thing that we all could use more of. Honestly, I expected this to be a simpler change, since it essentially reverts a previous PR, where its performance characteristics weren't explored at all beyond "it's a bit faster" - so putting this burden on "unlucky me" seems a bit unfair. I'm not working on Rust stuff full time, I'm doing this for fun (<sarcasm />), and with exams and more university courses coming up soon, I won't have the time to go spelunking for performance bottlenecks in the entire cargo code base anytime soon. Maybe this PR can serve as "prior research" if somebody wants to tackle dropping PS: Independent of this PR, it would probably make sense to add a few simple |
This does need to be done with care, but it is unfair to require that to be done by you. It would be better if we had isolated and repeatable benchmarks, and it is not good to claim that we care about performance without them. The current implementation of the resolver is a tangled mess and isolating benchmarks is hard. As one of the people who understands the performance of the resolver the best, let me give this a close look and see if that gives Alex the confidence to merge. The main reason this needs to be done carefully is that while #6336 was perf neutral, changes since then did rely on it. For example digging into why we needed to add Next let us look at
@decathorpe I should have left the resolver in a state that was easier to contribute to. I am sorry this has been a frustrating experience. I want to see this replacement happen, and will make shore your commits get used. Thank you for getting the ball rolling. |
@decathorpe as one peace of followup, Eric opened #9935 for us to discuss how best to add benchmarks. |
This PR drops cargo's dependency on im-rc. The
im-rc
andim
crates seem to be unmaintained, with no code changes, releases, or issue / PR triage in over a year. The linked issue also lists some logic / panic bugs that are still present in the latest im/im-rc releases, which have gone unfixed for at least a year, as well.A RUSTSEC advisory to mark
im
andim-rc
as unmaintained has been proposed, but the crate's author has apparently respondedsomewhere(I cannot even confirm that, since that conversation has not been linked in the issue) that the crates are still "maintained", but the GitHub project - both code and issues / PRs - has remained untouched since.The
im-rc
crate has now been gathering bug reports for over a year, while also having increasingly outdated crate dependencies (bitmaps
2 vs .3,rand_core
0.5 vs 0.6,rand_xoshiro
0.4 vs 0.6,arbitrary
0.4 vs. 1,proptest
0.9 vs. 1,quickcheck
0.9 vs .1, and dev-dependenciespretty_assertions
0.6 vs. 0.7,proptest_derive
0.1 vs. 0.3,rand
0.7 vs. 0.8).I'm not sure whether the original reason to use immutable data structures in cargo is still valid. In the PR where this dependency was introduced, the reasoning is that cloning some data structures is expensive, but benchmarks for comparing data structures from
std
and those fromim-rc
yielded mixed results. Some of the performance issues that the introduction ofim-rc
was supposed to work around have since been resolved in simpler ways.This PR removes the dependency on
im-rc
and makes the following changes:im_rc::HashMap
withstd::collections::HashMap
im_rc::HashSet
withstd::collections::HashSet
im_rc::OrdMap
withstd::collections::BTreeMap
im_rc::OrdSet
withstd::collections::BTreeSet
im_rc::hashmap::Entry
withstd::collections::hash_map::Entry
Additionally, data from a BTreeSet is temporarily moved into a
VecDeque
inRemainingDeps::pop_most_constrained
to make surepop_front
andpush_back
operations are not too slow (theBTreeSet::pop_first
method is still nightly-only under the experimentalmap_first_last
feature flag). The original code before the introduction ofim-rc
used a reversedstd::collections::BinaryHeap
max-heap for this purpose, which should work instead of temporarily moving data around, as well (though I have not ever used a BinaryHeap in this way before).I have run some simple benchmarks against my change to make sure performance does not regress. Since there's no official "bench"es in the cargo project, I just ran
cargo test
in both debug and release mode inhyperfine
before and after applying the changes from this PR, with theCFG_DISABLE_CROSS_TESTS=1
environment variable set, against the current nightly Rust toolchain (rustc 1.57.0-nightly (e30b68353 2021-09-05)
).Before this PR:
cargo test
(debug)cargo test --release
After this PR:
cargo test
(debug)cargo test --release
Assuming the unit and integration tests cover the changed code at all, the performance impact of dropping
im-rc
in favor of using collections from the standard library is either very very small (within the margin of error of the benchmark results), and if anything, ever so slightly in favor of thestd
collections inrelease
mode.As a nice side effect, the compiled
cargo
binary is also slightly smaller with this change, probably not only becauseim-rc
is dropped, but also due to removal / de-duplication of old versions of crates fromim-rc
's dependency tree.(compared on x86_64 linux target, with same rust toolchain)
Alternatively, there is now a fork of the
im
project (imbl), where a brave soul has started to triage all logic bugs and panic! issues that have not been triaged in the old project.