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

Turn off debuginfo for build dependencies to improve compile times #10493

Closed
wants to merge 10 commits into from

Conversation

lqd
Copy link
Member

@lqd lqd commented Mar 21, 2022

This PR is a draft towards the possible state of build-override defaults described in #10481, in order to improve compile times for build dependencies (mostly proc-macros and their dependencies benefit from this):

  • debuginfo is turned off by default (stripping and incremental are less impactful and can be done as improvements in future PRs, since they also require more analysis and work) to match this comment and zulip message
  • the new defaults are documented, and explain how to turn it back on when needed
  • when a build script fails, and backtraces are requested, a custom note mentions how to improve backtraces and links to the documentation

Opening as draft for feedback and guidance:

  • on the build script error message itself (it tries to only show when applicable, when backtraces are opted into, but I wonder if that interferes with how diagnostics are usually buffered). Update: the current approach and message look acceptable.
  • a bunch of tests rely on the fact that dependencies and build dependencies will not be rebuilt under the current defaults: now that the dev profile defaults and dev.build-override differ on whether debuginfo is turned on, these dependencies will be built twice. To leave the tests as is, either debuginfo could be turned off, or manually set to 2, which I've done in the "tmp: update tests relying on dev and build-deps reuse" commit. I am not sure it's the expected way to do this ? Update: the test changes look acceptable so I've turned the temporary commit into a permanent one.
  • I need some help for -Zscrape-examples: the feature seems to depend implicitly on a similar build reuse, with a mapping from for-host dependencies to regular dependencies. I'm not sure that this works correctly in all situations: it seems focused on feature flags, but reuse can also be different depending on other flags. In our case, debuginfo is now different, making some of the mapping memoization panic, causing the tests to fail. So I've temporarily disabled them in the "tmp: disable -Zscrape-examples unit tests" commit, and would need help to know what to do there. My expectation is that these panics would already happen today if someone manually opted into different debuginfo level in their build-override (or panic method, etc) but have not tested it (it seems sensible in a context where only the default settings have changed to a value users can already set). Update: This issue is now tracked in -Z rustdoc-scrape-examples panics with some profile overrides #10500. We've discussed it with @willcrichton, and I've incorporated a fix and tests in Fix docscrape memoization #10524.

Edit: with the updates above, this is ready to take out of draft.

The discussion issue #10481 also mentions the possible small strip addition that this PR doesn't do (and that could be done in the future), but since it's not a huge improvement, I'd say this closes #10481.

@rust-highfive
Copy link

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.

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Mar 21, 2022
profile.opt_level = InternedString::new("0");
profile.codegen_units = None;
profile.debuginfo = None;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this also add strip = "debuginfo" on all platforms but macOS?

Copy link
Member Author

@lqd lqd Mar 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the cargo team asked not to have strip for now in this message.

(incidentally, a friend tried the defaults I suggested and his project saw weird errors about unstable -Z flags being used on a stable compiler, because of stripping on android, so I'm not sure that all targets can use it rn)

@lqd lqd force-pushed the build-defaults branch from 56c5c10 to 1e95a15 Compare March 21, 2022 16:31
@joshtriplett
Copy link
Member

This looks great, thank you!

The approach to showing the message seems reasonable to me.

For tests that want to verify something only gets built once, turning on debug info in build-override seems reasonable.

-Zscrape-examples is an unstable feature; I'd suggest reaching out to the developers of -Zscrape-examples to work out what to do here. I don't think this feature should depend on reusing dependencies, if at all possible.

@lqd
Copy link
Member Author

lqd commented Mar 21, 2022

Thanks Josh, I'll do so ASAP.

(I've only now noticed I didn't open this PR as a draft GH PR as I intended to)

@lqd lqd changed the title Turn off debuginfo for build dependencies to improve compile times [WIP] Turn off debuginfo for build dependencies to improve compile times Mar 21, 2022
@lqd lqd marked this pull request as draft March 21, 2022 20:13
@lqd lqd force-pushed the build-defaults branch from 1e95a15 to 0b32184 Compare March 23, 2022 17:32
@lqd
Copy link
Member Author

lqd commented Mar 23, 2022

I updated the commits about:

  • fixing the tests relying on reuse, as josh said it looked OK
  • ignoring the scrape examples tests surfacing an existing problem, as I've opened a dedicated issue for that and it's not related to this PR

CI will fail because of #10500, and I'd assume this PR is kind of blocked on a resolution for that, otherwise -Z rustdoc-scrape-examples will likely stop working altogether.

@lqd
Copy link
Member Author

lqd commented Mar 29, 2022

-Zscrape-examples is an unstable feature; I'd suggest reaching out to the developers of -Zscrape-examples to work out what to do here.

I've reached out to @willcrichton about #10500, and turns out they had already fixed it amongst some of the changes in #10343.

#10343 may involve more coordination with rustc/rustdoc, waiting for stabilization, etc., compared to this PR so I've incorporated the minimum change to fix the issue here (with proper attribution) in case it's merged first, and added a few of the #10500 cases as tests.

With that fixed, and CI passing, this looks ready for review.

@lqd lqd changed the title [WIP] Turn off debuginfo for build dependencies to improve compile times Turn off debuginfo for build dependencies to improve compile times Mar 29, 2022
@lqd lqd marked this pull request as ready for review March 29, 2022 09:41
@ehuss
Copy link
Contributor

ehuss commented Mar 30, 2022

Can you split the docscrape change into a separate PR? I would prefer to track and review it separately.

@lqd lqd mentioned this pull request Mar 30, 2022
@lqd lqd force-pushed the build-defaults branch from fdab02e to 1407237 Compare March 30, 2022 21:58
@lqd
Copy link
Member Author

lqd commented Mar 30, 2022

@ehuss sure. I've split the commits into #10524.

lqd added 3 commits April 5, 2022 09:49
This describes the new defaults for build-overrides, and how to make sure backtraces have the usual debug info, when needed.
it's only displayed when backtraces are requested
it displays an additional message on how to improve these backtraces, now that debuginfo is turned off by default in `dev.build-override`.
@lqd lqd force-pushed the build-defaults branch from 1407237 to d5f1078 Compare April 5, 2022 07:49
@lqd
Copy link
Member Author

lqd commented Apr 5, 2022

Now that #10533 has landed, CI passes on this PR.

@ehuss
Copy link
Contributor

ehuss commented May 17, 2022

OK, sorry, I finally got around to posting the data:

https://docs.google.com/spreadsheets/d/1lru9ibjHLaXdFbROFIZXcvzXBKDDewGXjRYuvwJ0Xp8/edit?usp=sharing

To read this:

  • "Units Diff" - This is the number of extra rustc invocations.
  • "jxx" - This is the concurrency used.
  • "mean Factor" - This is the average factor of the wall-clock of several runs comparing before and after the PR. Example, 0.5 means this PR runs twice as fast as before the PR.
  • "user Factor" - The factor of the user-space time comparing before and after.
  • "system Factor" - The factor of the system-space time comparing before and after.

There's two sets of data. The one up top is from a Linux system was 16/32 CPUs. The bottom one is from a macOS system with 6/12 CPUs (it has fewer projects since I didn't want to wait for the rest).

There are little arrows in the headers where you can click to show hidden columns that contain the raw data.

This includes some projects that were intentionally selected to have skewed results (those that have a high count of shared dependencies) in order to examine some potentially poor scenarios. All of them should have some proc-macros or build scripts. I find it difficult to select "real world" projects, since they tend to be difficult to find, get access to, build correctly, or take too long to build.

@rust-lang/cargo Does anyone have any thoughts or conclusions you want to draw from this information? I'm still on the fence. It does look like wall clock time is improved in many situations, with a few situations made significantly worse like cargo-crev.

I feel like opportunistically sharing dependencies would be better, but I also feel like Cargo's "share things if possible" logic is already quite complex, and adding more logic to it would make it worse.

@epage
Copy link
Contributor

epage commented May 17, 2022

If I'm reading this correctly, it looks like the relative improvement is small except for

  • most cargo check runs
  • toml-rs and gluons test --no-run runs

I'm assuming cargo check made a big difference because build runs have the builds dominate, taking up a larger percentage. At first, I wondered about special profiles just for cargo check to make it even faster because of how often it is run but fresh-build check times don't matter as much as repeat runs.

I'm curious why we saw a difference with test --no-run in those two cases. We build more targets which includes more linking which I assume would have dominated things even more than build does over check, making the relative gains even smaller. So why is it doing better? Oh, are test targets built "for the host", so we aren't putting in debug info which saves us on link time which is dominating due to a lot of small test binaries? Is this a bug with the implementation as I assume people will be debugging their tests and want debug info? This would instead be helped if we explored automatic collection into a single test binary, mimicing what cargo does with its tests/testsuite.

@weihanglo
Copy link
Member

I find it difficult to select "real world" projects, since they tend to be difficult to find, get access to, build correctly, or take too long to build.

Any chance to collect more results from the new "Call For Testing" section in TWiR?

@lqd
Copy link
Member Author

lqd commented May 17, 2022

Oh, are test targets built "for the host", so we aren't putting in debug info which saves us on link time which is dominating due to a lot of small test binaries? Is this a bug with the implementation as I assume people will be debugging their tests and want debug info?

It is my understanding that test targets are not considered "for the host", no, nor that their defaults are changed by this PR. Only build dependencies are targeted.

I'm curious why we saw a difference with test --no-run in those two cases.

For toml-rs, for example, the reason you're seeing a noticeable improvement is that many of the proc-macros dependencies are enabled for tests only, and the total number of crates built with debuginfo is reduced from 20 to 10 (all the popular proc-macros libraries and their build scripts). That's the PR's goal, reducing the growing cost of building all these proc-macro dependencies whose debuginfo one cannot easily use in the first place, and its expected improvements when there are few shared dependencies. syn proc-macro2 quote etc. noticeably benefit from removing the debuginfo, and that translates well to their users.

Just to clarify, a high number of dependencies shared between the build-dependency subgraph and the dependency subgraph (a situation that I personally would think is less common than low-to-no sharing) is the only case where there can be regressions, and they can all be fixed by setting today's default value, to opt out of the debuginfo removal.

[profile.dev.build-override]
debug = 2

The crates that would be improved by this PR (in my results, or Eric's above) could equally well apply a debuginfo-removal override to their Cargo.toml and see the benefits. So the question looks to be more about which is the better default, to fall into the "pit of success" as we know users will rarely change defaults.

@epage
Copy link
Contributor

epage commented May 17, 2022

For toml-rs, for example, the reason you're seeing a noticeable improvement is that many of the proc-macros dependencies are enabled for tests only, and the total number of crates built with debuginfo is reduced from 20 to 10 (all the popular proc-macros libraries and their build scripts

Ah, I had missed that most of those dependencies were introduced exclusively for testing, thanks for finding the actual root cause!

So the question looks to be more about which is the better default, to fall into the "pit of success" as we know users will rarely change defaults.

I agree. I feel we should prioritize individuals, local and CI. Large projects or companies will devote more resources to optimizing things.

@lqd
Copy link
Member Author

lqd commented Feb 1, 2023

Closing this PR in favor of #11252.

@lqd lqd closed this Feb 1, 2023
@lqd lqd deleted the build-defaults branch February 1, 2023 22:29
bors added a commit that referenced this pull request Feb 1, 2023
Turn off debuginfo for build dependencies v2

This PR is an alternative to #10493, fixing its most important issue: the unit graph optimization to reuse already built artifacts for dependencies shared between the build-time and runtime subgraphs is preserved (most of the time).

By deferring the default debuginfo level in `dev.build-override` until unit graph sharing, we check whether re-use would happen. If so, we use the debuginfo level to ensure reuse does happen. Otherwise, we can avoid emitting debuginfo and improve compile times (on clean, debug and check builds -- although reuse only happens on debug builds).

I've kept the message explaining how to bump the debuginfo level if an error occurs while running a build script (and backtraces were requested) that was in the previous PR.

I've ran benchmarks on 800 crates, at different parallelism levels, and published the (surprisingly good) results with visualizations, summaries, and raw data [here](https://github.com/lqd/rustc-benchmarking-data/tree/main/experiments/cargo-build-defaults).

Opening this PR as discussed in [Eric's message](https://rust-lang.zulipchat.com/#narrow/stream/246057-t-cargo/topic/Defaults.20for.20faster.20compilation.20of.20.22for.20host.22.20units/near/304236576l) as draft since 2 tests won't pass. That fixes the `cargo-crev` case we saw as a blocker last time, but doesn't fix all such cases of reuse, like the 2 failing tests:

- [`optional_build_dep_and_required_normal_dep`](https://github.com/rust-lang/cargo/blob/642a0e625d10099a0ca289827de85499d073c572/tests/testsuite/build_script.rs#L4449)
- and [`proc_macro_ws`](https://github.com/rust-lang/cargo/blob/bd5db301b0c45ae540afcb19e030dd7c29d2ea4f/tests/testsuite/features2.rs#L1051)

These failures make sense, since the debuginfo optimization is done during traversal, it depends on the contents of the unit graph. These tests ensure that sharing happens even on finer-grained invocations: respectively, with an optional shared dependency that is later enabled, and building shared dependencies by themselves first and later as part of a complete workspace build.

In both these situations, there is no unit that is shared in the graph during the first invocation, so we do the optimization and remove the debuginfo. When the graph changes in the following invocation, sharing is present and we have to build the shared units again with debuginfo.

These cases feel rarer than `cargo-crev`'s case, but I do wonder if there's a way to fix them, or if it's acceptable to not support them.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Discussion of defaults settings for build dependencies for fastest compile times
7 participants