From 24150b9ddaa9d9144a8caf77a2325f12fc9cc620 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 13 Jul 2025 00:03:26 -0700 Subject: [PATCH 1/9] Call for Testing: Speeding up compilation with `hint-mostly-unused` --- .../call-for-testing-hint-mostly-unused.md | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 content/inside-rust/call-for-testing-hint-mostly-unused.md diff --git a/content/inside-rust/call-for-testing-hint-mostly-unused.md b/content/inside-rust/call-for-testing-hint-mostly-unused.md new file mode 100644 index 000000000..0f26da183 --- /dev/null +++ b/content/inside-rust/call-for-testing-hint-mostly-unused.md @@ -0,0 +1,167 @@ ++++ +path = "inside-rust/2025/07/14/call-for-testing-hint-mostly-unused" +title = "Call for Testing: Speeding up compilation with `hint-mostly-unused`" +authors = ["Josh Triplett"] ++++ + +I'm pleased to announce, and call for testing of, the nightly-only `rustc` +`-Zhint-mostly-unused` option, and the corresponding nightly Cargo features +`profile.hint-mostly-unused` and `hints.mostly-unused`. These options can help +accelerate your Rust compile time in some cases, by avoiding compilation of +items from your dependencies that you aren't using. Your feedback will help +evaluate these features and make progress towards stabilizing them in the +future. + +## Background + +Some crates provide comprehensive APIs with a very large surface area, yet many +of their users need only a few entry points. In such cases, the compiler +currently spends time generating code for the entire crate, and then ends up +throwing most of that code away. + +This can waste a substantial amount of compile time. Some large crates can take +minutes to compile, and when you use these large crates as dependencies, they +can take a disproportionate amount of the entire compilation time of your +top-level crate. + +In some cases, crates add feature flags to control compilation of their API +surface. This can improve compile time, but adds complexity for users, who now +need to determine which features they need for the APIs they use. Features also +constitute a stable interface of a crate, and changing feature flags can be a +breaking change. And even with feature flags, not every enabled function will +be needed; there is a balance between granularity and ease of use. + +## Deferring code generation with `-Zhint-mostly-unused` + +The latest nightly `rustc` compiler now supports an option +`-Zhint-mostly-unused`, which tells `rustc` that the crate's API surface will +mostly go unused. This is a hint, and `rustc` doesn't make guarantees about its +exact behavior (so that we can extend or improve it in the future), but +currently it causes the compiler to defer as much of code generation as +possible. + +Applying this option to key crates you depend on (and use only a small subset +of) can provide a substantial reduction in compile time, for debug builds and +especially for release builds. + +## How does this perform? + +| **Dependency Crate** | **Before** | **`hint-mostly-unused`** | **Delta** | +| :- | -: | -: | -: | +| `windows`, all Graphics/UI features | 18.3s | 10.7s | -42% | +| `windows`, all features | 3m 48s | 2m 55s | -23% | +| `rustix`, `all-apis` feature | 5.9s | 4.3s | -27% | +| `x11rb` and `x11rb-protocol` | 5.3s | 2.6s | -51% | +| `aws-sdk-ec2` | 4m 07s | 2m 04s | -50% | + +This performance improvement comes from deferring code generation. For +instance, the `windows` crate in the first example goes from building in 15.1s +of which 49% is codegen, to building in 7.5s of which 1% is codegen. + +Note that this option does not provide a universal performance improvement for +every crate. Using it for crates whose API surface is mostly used, and/or used +in multiple different crates or binaries (e.g. multiple test binaries that each +test a substantial swath of the API), may result in redoing code generation for +the same items repeatedly. + +## Plumbing this through Cargo with profiles + +In order to support compiling specific dependencies with this option, Cargo +supports a [profile option +`hint-mostly-unused`](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#profile-hint-mostly-unused-option) +to mark a crate with this hint: + +```toml +[profile.dev.package.huge-mostly-unused-dependency] +hint-mostly-unused = true + +[profile.release.package.huge-mostly-unused-dependency] +hint-mostly-unused = true +``` + +Note that if you build in multiple profiles (e.g. the default dev profile and +the `-r` release profile), you'll want to set this flag for both, as shown +above. + +Because this option is still nightly-only, and depends on a nightly-only +`rustc` option as well, enabling it requires passing +`-Zprofile-hint-mostly-unused` on the `cargo` command line. Without this +option, cargo will ignore this with a warning (but not an error, as it's still +just a hint). Note that as with any profile option, it only takes effect when +set in the top-level crate you're building. + +You should not, in general, set this flag for all your dependencies, or for +your own crate; you should set it selectively and test to make sure it provides +an improvement. Using the [cargo `--timings` +option](https://doc.rust-lang.org/nightly/cargo/reference/timings.html) can +help to identify crates that might benefit from this hint. And when testing +this hint, `--timings` can help detect whether the build time of *other* crates +in the dependency tree went up. + +## Making this automatic: Cargo `[hints]` + +A profile hint still requires the top-level crate to configure the hint for +some of its dependencies. However, some crates know that almost all of their +users will want this hint enabled. For such crates, we've introduced a new +`hints` mechanism in Cargo. Unlike profiles, which only apply when set in the +top-level crate you build, hints are set within individual crates in your +dependency graph. Hints provide a default behavior that you can still override. + +A crate that knows most of its users will not use most of its API surface can +set this hint in its `Cargo.toml` manifest: + +```toml +[hints] +mostly-unused = true +``` + +Note that setting a hint does *not* increase the Minimum Supported Rust Version +(MSRV) of your crate. Hints are always ignored if not understood. So, you can +safely set this hint immediately, without waiting for this feature to be +stabilized, and users of nightly will immediately benefit (if they pass +`-Zprofile-hint-mostly-unused` to cargo to enable the feature). + +### Future hints + +The `hints` mechanism in Cargo is a general feature, and we plan to make use of +it for other purposes in the future. For instance, we may offer a +`min-opt-level` option, for crates that are so performance-sensitive (e.g. +numerics code) that most users will want to build them with optimization even +in development mode. As with other hints, such a mechanism would still always +allow the top-level crate to override. + +## How do I help? + +We'd love for you to test out this feature on the latest Rust nightly compiler[^nightly]. + +[^nightly]: Make sure to run `rustup update nightly` (or however you manage your Rust releases). + +If you maintain a crate that has a large API surface, and you expect that the +typical user might use only a fraction of it, try setting `hints.mostly-unused` +in your `Cargo.toml`: + +```toml +[hints] +mostly-unused = true +``` + +You can test the effect of this by building a *typical* crate that depends on +your crate, with and without this hint set, using nightly Cargo: +`cargo +nightly -Zprofile-hint-mostly-unused build -r`. If this provides a +noticeable performance improvement, consider setting it in your published +crate. + +Please report any performance improvements, or unexpected performance issues, +or *especially* any failures you observe, to the [tracking issue for +profile-hint-mostly-unused](https://github.com/rust-lang/cargo/issues/15644). +We'll take this feedback into account to fix any issues with either the rustc +compiler feature or the Cargo features, and to evaluate when those features +have seen enough testing to be ready to stabilize. + +## Acknowledgements + +Much appreciation to: +- [Ben Kimock](https://github.com/saethlin), whose work towards MIR-only rlibs + provided inspiration and infrastructure for this work. +- The [Rust All Hands](https://rustweek.org/all-hands/) and its organizers, for + providing a forum to discuss and progress this work. From 003dedf97212bd589b3c9933011b45c98b05552a Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 13 Jul 2025 02:19:02 -0700 Subject: [PATCH 2/9] Clarify performance numbers --- content/inside-rust/call-for-testing-hint-mostly-unused.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/content/inside-rust/call-for-testing-hint-mostly-unused.md b/content/inside-rust/call-for-testing-hint-mostly-unused.md index 0f26da183..5b91aa95f 100644 --- a/content/inside-rust/call-for-testing-hint-mostly-unused.md +++ b/content/inside-rust/call-for-testing-hint-mostly-unused.md @@ -46,6 +46,9 @@ especially for release builds. ## How does this perform? +Some build timings for clean release builds of a crate depending on various +specific large API crates: + | **Dependency Crate** | **Before** | **`hint-mostly-unused`** | **Delta** | | :- | -: | -: | -: | | `windows`, all Graphics/UI features | 18.3s | 10.7s | -42% | @@ -64,6 +67,9 @@ in multiple different crates or binaries (e.g. multiple test binaries that each test a substantial swath of the API), may result in redoing code generation for the same items repeatedly. +Also note that this only provides a performance win if you are rebuilding the +dependency. If you're only rebuilding the top-level crate, this won't help. + ## Plumbing this through Cargo with profiles In order to support compiling specific dependencies with this option, Cargo From b5846cbbb3db67acbb33f30cc264afec5807e332 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 13 Jul 2025 02:19:10 -0700 Subject: [PATCH 3/9] Add more details to call for testing --- .../call-for-testing-hint-mostly-unused.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/content/inside-rust/call-for-testing-hint-mostly-unused.md b/content/inside-rust/call-for-testing-hint-mostly-unused.md index 5b91aa95f..2e7b44476 100644 --- a/content/inside-rust/call-for-testing-hint-mostly-unused.md +++ b/content/inside-rust/call-for-testing-hint-mostly-unused.md @@ -157,9 +157,30 @@ your crate, with and without this hint set, using nightly Cargo: noticeable performance improvement, consider setting it in your published crate. +If you're building atop a crate that you only use a small fraction of, you can +try setting the profile option in your own crate: + +```toml +[profile.dev.package.huge-mostly-unused-dependency] +hint-mostly-unused = true + +[profile.release.package.huge-mostly-unused-dependency] +hint-mostly-unused = true +``` + Please report any performance improvements, or unexpected performance issues, or *especially* any failures you observe, to the [tracking issue for profile-hint-mostly-unused](https://github.com/rust-lang/cargo/issues/15644). +When reporting, please tell us: +- What hints or profile settings you added +- What crates you added them to +- What top-level crate you're building +- What features you set when building +- What build profile you're using (e.g. the default dev profile, or the release + profile) +- Whether you did a clean build or an incremental rebuild +- What performance numbers you got with and without the option you added + We'll take this feedback into account to fix any issues with either the rustc compiler feature or the Cargo features, and to evaluate when those features have seen enough testing to be ready to stabilize. From 3bf54b3cee88874013afdb48f82626aa74fdb929 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 13 Jul 2025 12:08:07 -0700 Subject: [PATCH 4/9] Make the potential downsides much more emphatic and clear --- .../call-for-testing-hint-mostly-unused.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/content/inside-rust/call-for-testing-hint-mostly-unused.md b/content/inside-rust/call-for-testing-hint-mostly-unused.md index 2e7b44476..a35043af3 100644 --- a/content/inside-rust/call-for-testing-hint-mostly-unused.md +++ b/content/inside-rust/call-for-testing-hint-mostly-unused.md @@ -62,10 +62,14 @@ instance, the `windows` crate in the first example goes from building in 15.1s of which 49% is codegen, to building in 7.5s of which 1% is codegen. Note that this option does not provide a universal performance improvement for -every crate. Using it for crates whose API surface is mostly used, and/or used -in multiple different crates or binaries (e.g. multiple test binaries that each -test a substantial swath of the API), may result in redoing code generation for -the same items repeatedly. +every crate; if used when not applicable, this option can make builds much +*slower*. Deferring compilation of the items in a crate can lead to redoing +code generation for those items repeatedly. In particular, avoid using this +hint for crates whose API surface is mostly used, and/or used in multiple +different crates or binaries (e.g. multiple test binaries that each test a +substantial swath of the API). Always do performance analysis when considering +this hint, and only apply it if it applies obvious and substantial wins for +your users. Also note that this only provides a performance win if you are rebuilding the dependency. If you're only rebuilding the top-level crate, this won't help. From 90d0bf6a84f74034192e4f6b2385a5336f59ec08 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 13 Jul 2025 12:14:43 -0700 Subject: [PATCH 5/9] Mention that code gets thrown away because it's unused --- content/inside-rust/call-for-testing-hint-mostly-unused.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/inside-rust/call-for-testing-hint-mostly-unused.md b/content/inside-rust/call-for-testing-hint-mostly-unused.md index a35043af3..9701bc983 100644 --- a/content/inside-rust/call-for-testing-hint-mostly-unused.md +++ b/content/inside-rust/call-for-testing-hint-mostly-unused.md @@ -17,7 +17,7 @@ future. Some crates provide comprehensive APIs with a very large surface area, yet many of their users need only a few entry points. In such cases, the compiler currently spends time generating code for the entire crate, and then ends up -throwing most of that code away. +throwing most of that code away as unused. This can waste a substantial amount of compile time. Some large crates can take minutes to compile, and when you use these large crates as dependencies, they From f76bfcc6616551d317e6b0bc84edbb1ec87f4796 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 13 Jul 2025 12:15:25 -0700 Subject: [PATCH 6/9] Mention that the hint is redundant with polymorphic items --- content/inside-rust/call-for-testing-hint-mostly-unused.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/content/inside-rust/call-for-testing-hint-mostly-unused.md b/content/inside-rust/call-for-testing-hint-mostly-unused.md index 9701bc983..faf6c8387 100644 --- a/content/inside-rust/call-for-testing-hint-mostly-unused.md +++ b/content/inside-rust/call-for-testing-hint-mostly-unused.md @@ -71,6 +71,10 @@ substantial swath of the API). Always do performance analysis when considering this hint, and only apply it if it applies obvious and substantial wins for your users. +If most of the items in your crate are polymorphic (generic), this hint may be +redundant, as Rust already defers compilation of polymorphic items until they +get monomorphized with specific types. + Also note that this only provides a performance win if you are rebuilding the dependency. If you're only rebuilding the top-level crate, this won't help. From 322e33a4c64ba3febdf12f0d16edf3cf88827000 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 13 Jul 2025 12:55:59 -0700 Subject: [PATCH 7/9] Expand opening background --- .../call-for-testing-hint-mostly-unused.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/content/inside-rust/call-for-testing-hint-mostly-unused.md b/content/inside-rust/call-for-testing-hint-mostly-unused.md index faf6c8387..1a1e143a3 100644 --- a/content/inside-rust/call-for-testing-hint-mostly-unused.md +++ b/content/inside-rust/call-for-testing-hint-mostly-unused.md @@ -14,11 +14,16 @@ future. ## Background -Some crates provide comprehensive APIs with a very large surface area, yet many -of their users need only a few entry points. In such cases, the compiler -currently spends time generating code for the entire crate, and then ends up +When building a Rust library crate, the compiler generates compiled code for as +much of the crate as it can[^1], which +gets linked into later crates into the dependency graph. However, some crates +provide comprehensive APIs with a very large surface area, yet many of their +users need only a few entry points. In such cases, the compiler currently +spends time generating code for the entire crate, and the linker then ends up throwing most of that code away as unused. +[^1]: Roughly, everything that isn't generic and isn't inlined. + This can waste a substantial amount of compile time. Some large crates can take minutes to compile, and when you use these large crates as dependencies, they can take a disproportionate amount of the entire compilation time of your From 10c0a3f9b89b7b45cd16fd38d1739018095e6014 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 13 Jul 2025 12:57:02 -0700 Subject: [PATCH 8/9] Don't use a footnote --- .../inside-rust/call-for-testing-hint-mostly-unused.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/content/inside-rust/call-for-testing-hint-mostly-unused.md b/content/inside-rust/call-for-testing-hint-mostly-unused.md index 1a1e143a3..ccb277202 100644 --- a/content/inside-rust/call-for-testing-hint-mostly-unused.md +++ b/content/inside-rust/call-for-testing-hint-mostly-unused.md @@ -15,15 +15,13 @@ future. ## Background When building a Rust library crate, the compiler generates compiled code for as -much of the crate as it can[^1], which -gets linked into later crates into the dependency graph. However, some crates -provide comprehensive APIs with a very large surface area, yet many of their -users need only a few entry points. In such cases, the compiler currently +much of the crate as it can (everything that isn't generic and isn't inlined), +which gets linked into later crates into the dependency graph. However, some +crates provide comprehensive APIs with a very large surface area, yet many of +their users need only a few entry points. In such cases, the compiler currently spends time generating code for the entire crate, and the linker then ends up throwing most of that code away as unused. -[^1]: Roughly, everything that isn't generic and isn't inlined. - This can waste a substantial amount of compile time. Some large crates can take minutes to compile, and when you use these large crates as dependencies, they can take a disproportionate amount of the entire compilation time of your From e3b53def024b68c36500f45ab257e9ef13d4f275 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 13 Jul 2025 16:32:29 -0700 Subject: [PATCH 9/9] Re-emphasize that the hint shouldn't be applied to all your dependencies --- .../inside-rust/call-for-testing-hint-mostly-unused.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/content/inside-rust/call-for-testing-hint-mostly-unused.md b/content/inside-rust/call-for-testing-hint-mostly-unused.md index ccb277202..3eda9a4a9 100644 --- a/content/inside-rust/call-for-testing-hint-mostly-unused.md +++ b/content/inside-rust/call-for-testing-hint-mostly-unused.md @@ -70,9 +70,11 @@ every crate; if used when not applicable, this option can make builds much code generation for those items repeatedly. In particular, avoid using this hint for crates whose API surface is mostly used, and/or used in multiple different crates or binaries (e.g. multiple test binaries that each test a -substantial swath of the API). Always do performance analysis when considering -this hint, and only apply it if it applies obvious and substantial wins for -your users. +substantial swath of the API). + +Always do performance analysis when considering this hint, and only apply it if +it applies obvious and substantial wins for your users. Never apply it across +the board to all your dependencies. If most of the items in your crate are polymorphic (generic), this hint may be redundant, as Rust already defers compilation of polymorphic items until they