|
| 1 | ++++ |
| 2 | +path = "inside-rust/2025/07/14/call-for-testing-hint-mostly-unused" |
| 3 | +title = "Call for Testing: Speeding up compilation with `hint-mostly-unused`" |
| 4 | +authors = ["Josh Triplett"] |
| 5 | +aliases = ["inside-rust/2025/07/14/call-for-testing-hint-mostly-unused.html"] |
| 6 | ++++ |
| 7 | + |
| 8 | +I'm pleased to announce, and call for testing of, the nightly-only `rustc` |
| 9 | +`-Zhint-mostly-unused` option, and the corresponding nightly Cargo features |
| 10 | +`profile.hint-mostly-unused` and `hints.mostly-unused`. These options can help |
| 11 | +accelerate your Rust compile time in some cases, by avoiding compilation of |
| 12 | +items from your dependencies that you aren't using. Your feedback will help |
| 13 | +evaluate these features and make progress towards stabilizing them in the |
| 14 | +future. |
| 15 | + |
| 16 | +## Background |
| 17 | + |
| 18 | +Some crates provide comprehensive APIs with a very large surface area, yet many |
| 19 | +of their users need only a few entry points. In such cases, the compiler |
| 20 | +currently spends time generating code for the entire crate, and then ends up |
| 21 | +throwing most of that code away. |
| 22 | + |
| 23 | +This can waste a substantial amount of compile time. Some large crates can take |
| 24 | +minutes to compile, and when you use these large crates as dependencies, they |
| 25 | +can take a disproportionate amount of the entire compilation time of your |
| 26 | +top-level crate. |
| 27 | + |
| 28 | +In some cases, crates add feature flags to control compilation of their API |
| 29 | +surface. This can improve compile time, but adds complexity for users, who now |
| 30 | +need to determine which features they need for the APIs they use. Features also |
| 31 | +constitute a stable interface of a crate, and changing feature flags can be a |
| 32 | +breaking change. And even with feature flags, not every enabled function will |
| 33 | +be needed; there is a balance between granularity and ease of use. |
| 34 | + |
| 35 | +## Deferring code generation with `-Zhint-mostly-unused` |
| 36 | + |
| 37 | +The latest nightly `rustc` compiler now supports an option |
| 38 | +`-Zhint-mostly-unused`, which tells `rustc` that the crate's API surface will |
| 39 | +mostly go unused. This is a hint, and `rustc` doesn't make guarantees about its |
| 40 | +exact behavior (so that we can extend or improve it in the future), but |
| 41 | +currently it causes the compiler to defer as much of code generation as |
| 42 | +possible. (The compiler does this using the same machinery as |
| 43 | +`#[inline(always)]`.) |
| 44 | + |
| 45 | +Applying this option to key crates you depend on (and use only a small subset |
| 46 | +of) can provide a substantial reduction in compile time, for debug builds and |
| 47 | +especially for release builds. |
| 48 | + |
| 49 | +## How does this perform? |
| 50 | + |
| 51 | +| **Dependency Crate** | **Before** | **`hint-mostly-unused`** | **Delta** | |
| 52 | +| :- | -: | -: | -: | |
| 53 | +| `windows`, all Graphics/UI features | 18.3s | 10.7s | -42% | |
| 54 | +| `windows`, all features | 3m 48s | 2m 55s | -23% | |
| 55 | +| `rustix`, `all-apis` feature | 5.9s | 4.3s | -27% | |
| 56 | +| `x11rb` and `x11rb-protocol` | 5.3s | 2.6s | -51% | |
| 57 | +| `aws-sdk-ec2` | 4m 07s | 2m 04s | -50% | |
| 58 | + |
| 59 | +This performance improvement comes from deferring code generation. For |
| 60 | +instance, the `windows` crate in the first example goes from building in 15.1s |
| 61 | +of which 49% is codegen, to building in 7.5s of which 1% is codegen. |
| 62 | + |
| 63 | +Note that this option does not provide a universal performance improvement for |
| 64 | +every crate. Using it for crates whose API surface is mostly used, and/or used |
| 65 | +in multiple different crates or binaries (e.g. multiple test binaries that each |
| 66 | +test a substantial swath of the API), may result in redoing code generation for |
| 67 | +the same items repeatedly. |
| 68 | + |
| 69 | +## Plumbing this through Cargo with profiles |
| 70 | + |
| 71 | +In order to support compiling specific dependencies with this option, Cargo |
| 72 | +supports a [profile option |
| 73 | +`hint-mostly-unused`](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#profile-hint-mostly-unused-option) |
| 74 | +to mark a crate with this hint: |
| 75 | + |
| 76 | +```toml |
| 77 | +[profile.dev.package.huge-mostly-unused-dependency] |
| 78 | +hint-mostly-unused = true |
| 79 | + |
| 80 | +[profile.release.package.huge-mostly-unused-dependency] |
| 81 | +hint-mostly-unused = true |
| 82 | +``` |
| 83 | + |
| 84 | +Note that if you build in multiple profiles (e.g. the default dev profile and |
| 85 | +the `-r` release profile), you'll want to set this flag for both, as shown |
| 86 | +above. |
| 87 | + |
| 88 | +Because this option is still nightly-only, and depends on a nightly-only |
| 89 | +`rustc` option as well, enabling it requires passing |
| 90 | +`-Zprofile-hint-mostly-unused` on the `cargo` command line. Without this |
| 91 | +option, cargo will ignore this with a warning (but not an error, as it's still |
| 92 | +just a hint). Note that as with any profile option, it only takes effect when |
| 93 | +set in the top-level crate you're building. |
| 94 | + |
| 95 | +You should not, in general, set this flag for all your dependencies, or for |
| 96 | +your own crate; you should set it selectively and test to make sure it provides |
| 97 | +an improvement. Using the [cargo `--timings` |
| 98 | +option](https://doc.rust-lang.org/nightly/cargo/reference/timings.html) can |
| 99 | +help to identify crates that might benefit from this hint. And when testing |
| 100 | +this hint, `--timings` can help detect whether the build time of *other* crates |
| 101 | +in the dependency tree went up. |
| 102 | + |
| 103 | +## Making this automatic: Cargo `[hints]` |
| 104 | + |
| 105 | +A profile hint still requires the top-level crate to configure the hint for |
| 106 | +some of its dependencies. However, some crates know that almost all of their |
| 107 | +users will want this hint enabled. For such crates, we've introduced a new |
| 108 | +`hints` mechanism in Cargo. Unlike profiles, which only apply when set in the |
| 109 | +top-level crate you build, hints are set within individual crates in your |
| 110 | +dependency graph. Hints provide a default behavior that you can still override. |
| 111 | + |
| 112 | +A crate that knows most of its users will not use most of its API surface can |
| 113 | +set this hint in its `Cargo.toml` manifest: |
| 114 | + |
| 115 | +```toml |
| 116 | +[hints] |
| 117 | +mostly-unused = true |
| 118 | +``` |
| 119 | + |
| 120 | +Note that setting a hint does *not* increase the Minimum Supported Rust Version |
| 121 | +(MSRV) of your crate. Hints are always ignored if not understood. So, you can |
| 122 | +safely set this hint immediately, without waiting for this feature to be |
| 123 | +stabilized, and users of nightly will immediately benefit (if they pass |
| 124 | +`-Zprofile-hint-mostly-unused` to cargo to enable the feature). |
| 125 | + |
| 126 | +### Future hints |
| 127 | + |
| 128 | +The `hints` mechanism in Cargo is a general feature, and we plan to make use of |
| 129 | +it for other purposes in the future. For instance, we may offer a |
| 130 | +`min-opt-level` option, for crates that are so performance-sensitive (e.g. |
| 131 | +numerics code) that most users will want to build them with optimization even |
| 132 | +in development mode. As with other hints, such a mechanism would still always |
| 133 | +allow the top-level crate to override. |
| 134 | + |
| 135 | +## How do I help? |
| 136 | + |
| 137 | +We'd love for you to test out this feature on the latest Rust nightly compiler[^nightly]. |
| 138 | + |
| 139 | +[^nightly]: Make sure to run `rustup update nightly` (or however you manage your Rust releases). |
| 140 | + |
| 141 | +If you maintain a crate that has a large API surface, and you expect that the |
| 142 | +typical user might use only a fraction of it, try setting `hints.mostly-unused` |
| 143 | +in your `Cargo.toml`: |
| 144 | + |
| 145 | +```toml |
| 146 | +[hints] |
| 147 | +mostly-unused = true |
| 148 | +``` |
| 149 | + |
| 150 | +You can test the effect of this by building a *typical* crate that depends on |
| 151 | +your crate, with and without this hint set, using nightly Cargo: |
| 152 | +`cargo +nightly -Zprofile-hint-mostly-unused build -r`. If this provides a |
| 153 | +noticeable performance improvement, consider setting it in your published |
| 154 | +crate. |
| 155 | + |
| 156 | +Please report any performance improvements, or unexpected performance issues, |
| 157 | +or *especially* any failures you observe, to the [tracking issue for |
| 158 | +profile-hint-mostly-unused](https://github.com/rust-lang/cargo/issues/15644). |
| 159 | +We'll take this feedback into account to fix any issues with either the rustc |
| 160 | +compiler feature or the Cargo features, and to evaluate when those features |
| 161 | +have seen enough testing to be ready to stabilize. |
| 162 | + |
| 163 | +## Acknowledgements |
| 164 | + |
| 165 | +Much appreciation to: |
| 166 | +- [Ben Kimock](https://github.com/saethlin), whose work towards MIR-only rlibs |
| 167 | + provided inspiration and infrastructure for this work. |
| 168 | +- The [Rust All Hands](https://rustweek.org/all-hands/) and its organizers, for |
| 169 | + providing a forum to discuss and progress this work. |
0 commit comments