You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Tracking Issue for optimize_for_size standard library feature #125612
#125011 has added a optimize_for_size feature to the standard library, which is designed for special casing some algorithms in core/alloc/std to provide an implementation that tries to minimize its effect on binary size.
To use this feature, the standard library has to be compiled using -Zbuild-std:
Small aside, but do you think that code using optimize_for_size should be always optimized for size, or only in release mode? Since a lot of the submitted PRs use cfg!(feature = "optimize_for_size") to test, and I'm not sure that dead code removal will always work in these cases without optimizations enabled.
Obviously, to get the best benefits, you're going to want optimizations anyway, but I'm thinking that we probably do want to ensure that code actually isn't compiled in all cases using conditional compilation, rather than just conditions. This also might help building on memory-constrained environments since the code won't have to be kept in memory before it's removed.
I also think that longer-term, there should be an easier flag to enable this feature that also takes into account optimization options (making sure loops aren't unrolled, etc.) but at least for now, while it's just a Cargo feature, it makes sense to at least figure out what cases we should optimize for.
in practice we've been using the cfg!(feature = "optimize_for_size") macro a bunch, which would rely on the optimizer evaluating and simplifying boolean conditions and then removing branches or blocks. I suspect in practice this is reliable enough. Certainly on small examples in godbolt this seems to work fine.
More generally, the point of this flag is really --release mode. The size of a debug binary is just not representative at all. Especially on embedded, --release is effectively standard: you don't want to wait for your debug binary to be pushed through a wire to your device. Because optimization passes in general don't really give guarantees I think it's fine that the optimizer may miss some opportunities in a debug build.
I say that in part because using proper conditional compilation would make the code harder to maintain, to the point where we may require completely separate functions for when the flag is enabled or not. We've been trying to prevent completely separate implementations from happening so far: that won't always work but when we can I think the result is better.
Reacted by Clar Fon, lolbinarycat and Trevor Gross
since #[cfg(...)] will prevent the code from even compiling, whereas this will compile the code but leave it inside a dead branch under if false.
I say that in part because using proper conditional compilation would make the code harder to maintain, to the point where we may require completely separate functions for when the flag is enabled or not. We've been trying to prevent completely separate implementations from happening so far: that won't always work but when we can I think the result is better.
I agree here, mostly just wanted to ask because options do exist (like the currently unstable cfg_match macro). And while you might not explicitly push debug code to a device, you might run tests on it off the device in debug mode, and it's important to ensure that these tests are accurate. I can imagine also wanting to compare generated assembly between debug and release mode to see what optimisations are done, without having the non-size-optimised code getting in the way.
Basically agree with the current implementation, but think it's important to properly clarify so folks are aware of the best way to implement things.
Ah, sorry, I didn't realize that with cfg! it is different. Well, I would be also extremely surprised if if false survived even in debug mode 😆 But happy to see a counterexample.
Hi! I tested this option on my embedded project using embassy on a STM32F401 and my code size did go up from 335kB to 360kB. I can do some debugging/investigation why it happens if giving some instructions what to test for.
Thanks!
Ah, I think I found the issue, adding -Z build-std-features="std/optimize_for_size" to cargo build overrides build-std-features from cargo config.toml and that disabled panic_immediate_abort when building my project and bumped the code size. When I add -Zbuild-std-features="std/optimize_for_size panic_immediate_abort" my code size goes down:
with "std/optimize_for_size"
text data bss dec hex filename
339432 2800 35452 377684 5c354 init-stm32
without:
text data bss dec hex filename
340176 2800 35452 378428 5c63c init-stm32
Not a lot but it is something. Thanks!
Btw. small nitpick, why the reason for the std/ prefix? Since options like panic_immediate_abort don't have it, sounds kind redundant when it is already in build-std-features
Hmm, that's weird, on CI we enable it with just optimize_for_size, and in Cargo.toml of stdlib it seems to be defined in the same way as panic_immediate_abort (https://github.com/rust-lang/rust/pull/125011/files).
I think that optimize_for_size is enough. This works for me:
Hmm, that's weird, on CI we enable it with just optimize_for_size, and in Cargo.toml of stdlib it seems to be defined in the same way as panic_immediate_abort (https://github.com/rust-lang/rust/pull/125011/files).
I think that optimize_for_size is enough. This works for me:
In general, it's preferred to open an issue instead of talking on the tracking issue. So, yes.
I don't think we actually record the size for optimize_for_size on builds to check for large regressions. That's potentially something we'd like to check.
@clarfonthey To be clear, I wasn't talking about a regression in the size of the generated binary. The regression consists in programs failing to build entirely since a specific nightly toolchain version.
According to your suggestion, I just opened this issue.
@clarfonthey And yet, builds did start failing in a project with panic=unwrap since a specific nightly version, and the fix was to add "panic-unwind" in build-std-features. Not sure if this is intended behavior, but that wasn't necessary before a nightly version.
Activity
clarfonthey commentedon May 29, 2024
Small aside, but do you think that code using
optimize_for_sizeshould be always optimized for size, or only in release mode? Since a lot of the submitted PRs usecfg!(feature = "optimize_for_size")to test, and I'm not sure that dead code removal will always work in these cases without optimizations enabled.Obviously, to get the best benefits, you're going to want optimizations anyway, but I'm thinking that we probably do want to ensure that code actually isn't compiled in all cases using conditional compilation, rather than just conditions. This also might help building on memory-constrained environments since the code won't have to be kept in memory before it's removed.
I also think that longer-term, there should be an easier flag to enable this feature that also takes into account optimization options (making sure loops aren't unrolled, etc.) but at least for now, while it's just a Cargo feature, it makes sense to at least figure out what cases we should optimize for.
Kobzol commentedon May 29, 2024
I would be extremely surprised if any cfg-ed out code survived even in debug mode without optimizations. Even something like this:
Doesn't contain the
helloruststring in the final binary in a debug build when the feature is disabled.folkertdev commentedon May 29, 2024
in practice we've been using the
cfg!(feature = "optimize_for_size")macro a bunch, which would rely on the optimizer evaluating and simplifying boolean conditions and then removing branches or blocks. I suspect in practice this is reliable enough. Certainly on small examples in godbolt this seems to work fine.More generally, the point of this flag is really
--releasemode. The size of a debug binary is just not representative at all. Especially on embedded,--releaseis effectively standard: you don't want to wait for your debug binary to be pushed through a wire to your device. Because optimization passes in general don't really give guarantees I think it's fine that the optimizer may miss some opportunities in a debug build.I say that in part because using proper conditional compilation would make the code harder to maintain, to the point where we may require completely separate functions for when the flag is enabled or not. We've been trying to prevent completely separate implementations from happening so far: that won't always work but when we can I think the result is better.
clarfonthey commentedon May 29, 2024
Right, note that this is different from:
since
#[cfg(...)]will prevent the code from even compiling, whereas this will compile the code but leave it inside a dead branch underif false.I agree here, mostly just wanted to ask because options do exist (like the currently unstable
cfg_matchmacro). And while you might not explicitly push debug code to a device, you might run tests on it off the device in debug mode, and it's important to ensure that these tests are accurate. I can imagine also wanting to compare generated assembly between debug and release mode to see what optimisations are done, without having the non-size-optimised code getting in the way.Basically agree with the current implementation, but think it's important to properly clarify so folks are aware of the best way to implement things.
Kobzol commentedon May 29, 2024
Ah, sorry, I didn't realize that with
cfg!it is different. Well, I would be also extremely surprised ifif falsesurvived even in debug mode 😆 But happy to see a counterexample.dragonnn commentedon Jun 11, 2024
Hi! I tested this option on my embedded project using embassy on a STM32F401 and my code size did go up from 335kB to 360kB. I can do some debugging/investigation why it happens if giving some instructions what to test for.
Thanks!
Kobzol commentedon Jun 11, 2024
Did it go up vs just using
-Zbuild-stdwithout theoptimize_for_sizefeature, or versus a normal cargo build with a pre-built libstd?dragonnn commentedon Jun 11, 2024
Ah, I think I found the issue, adding
-Z build-std-features="std/optimize_for_size"tocargo buildoverridesbuild-std-featuresfrom cargo config.toml and that disabledpanic_immediate_abortwhen building my project and bumped the code size. When I add-Zbuild-std-features="std/optimize_for_size panic_immediate_abort"my code size goes down:with
"std/optimize_for_size"without:
Not a lot but it is something. Thanks!
Btw. small nitpick, why the reason for the std/ prefix? Since options like
panic_immediate_abortdon't have it, sounds kind redundant when it is already inbuild-std-featuresKobzol commentedon Jun 11, 2024
Hmm, that's weird, on CI we enable it with just
optimize_for_size, and inCargo.tomlof stdlib it seems to be defined in the same way aspanic_immediate_abort(https://github.com/rust-lang/rust/pull/125011/files).I think that
optimize_for_sizeis enough. This works for me:diondokter commentedon Jun 11, 2024
With what's currently merged, the max you can get is ~1.5kb savings on opt-level z.
There's certainly more to be done for sure :)
dragonnn commentedon Jun 11, 2024
Oh, right it works both way, doesn't metter if it starts with std/ or not. I assumed from the first posts it needs to start with std/. Thanks!
Kobzol commentedon Jun 11, 2024
Sorry, that was an error on my part, fixed the code example. Thanks!
5 remaining items
Auto merge of #125720 - folkertdev:optimize_for_size-ptr-rotate, r=Am…
Rollup merge of rust-lang#126271 - diondokter:dec2flt-skip-fast-path,…
Rollup merge of rust-lang#126271 - diondokter:dec2flt-skip-fast-path,…
Unrolled build for rust-lang#126271
Auto merge of rust-lang#129587 - Voultapher:opt-for-size-variants-of-…
Auto merge of #129587 - Voultapher:opt-for-size-variants-of-sort-impl…
Auto merge of #129587 - Voultapher:opt-for-size-variants-of-sort-impl…
Auto merge of #125720 - folkertdev:optimize_for_size-ptr-rotate, r=Am…
Auto merge of #129587 - Voultapher:opt-for-size-variants-of-sort-impl…
DavideCannizzo commentedon Sep 12, 2026
It seems like
optimize_for_sizehas had a regression and is breaking builds on newer Rust versions.I have built this reproducer. Everything is explained in the README.md in the reproducer repository.
Should I open a new issue?
clarfonthey commentedon Sep 12, 2026
In general, it's preferred to open an issue instead of talking on the tracking issue. So, yes.
I don't think we actually record the size for
optimize_for_sizeon builds to check for large regressions. That's potentially something we'd like to check.DavideCannizzo commentedon Sep 12, 2026
@clarfonthey To be clear, I wasn't talking about a regression in the size of the generated binary. The regression consists in programs failing to build entirely since a specific nightly toolchain version.
According to your suggestion, I just opened this issue.
clarfonthey commentedon Sep 13, 2026
Ah, that makes a lot more sense. To be clear, I was specifically excluding the case where builds failed since we explicitly cover that case:
rust/src/ci/docker/scripts/x86_64-gnu-llvm3.sh
Lines 19 to 21 in 24d4720
(Note, this is just for x86_64 Linux, but for now we don't seem to have any target-specific code under this feature.)
DavideCannizzo commentedon Sep 13, 2026
@clarfonthey And yet, builds did start failing in a project with
panic=unwrapsince a specific nightly version, and the fix was to add"panic-unwind"inbuild-std-features. Not sure if this is intended behavior, but that wasn't necessary before a nightly version.clarfonthey commentedon Sep 13, 2026
Right, and note that the bootstrap for this repo accounts for that and is thus different from cargo's
build-stdfeature.