Repository navigation
Refactor core::cmp::{smallest, largest} & add mir-opt test - #161275
Conversation
|
rustbot has assigned @JonathanBrouwer. Use Why was this reviewer chosen?The reviewer was selected based on:
|
|
r? libs |
I'm wondering if it would be easier/better to show this on -Cno-prepopulate-passes LLVM IR? That should be able to do it without duplicating core's impl, as long as the impls in core are cc @scottmcm Looking at the actual impl here, I guess the reason we're pulling out two tuple 'parts' is to simplify the common case of having smallest/largest with just two arguments passed? I'm wondering if we'd get better codegen in debug mode if we made this more explicitly a loop, by transmuting the (T, T, ...) into [T; N] and then going through Iterator::{min, max}? I'm pretty sure we don't guarantee enough about repr(Rust) to make that transmute always compile, but if it does, then I think it is a sound way to access the elements? In particular I suspect doing it via min/max on the array makes it easier for LLVM to notice the loop and not lower to "forcefully" unrolled code... while hopefully not making the common (short) case much worse. |
There was a problem hiding this comment.
I think what we probably want here is a pre-codegen MIR test: https://github.com/rust-lang/rust/tree/main/tests/mir-opt/pre-codegen
Notably, those do run mir optimizations, so it should be fine to call cmp::min and cmp::smallest and see what they do. (Which, after #161081, should both just be "call the compiler intrinsic.)
If open-coded smallest will have more overhead in unoptimized, but I'm not actually that worried about it. We always ship the sysroot with mir optimizations, for example.
I mostly just want to see that the various method layers properly fold away thanks to the mir-opt-level=2 when people call the std methods. (This is similar to the tests that make sure that passing tiny closures to Option::map can get inlined so it's not gratuitously different from a manual if-let.)
TBH it might not even need any changes to the implementation, since https://rust.godbolt.org/z/cGPqao9jf is already looking pretty reasonable. (That nightly doesn't have the min intrinsic, though, so you'll see in a test here will probably be different.)
|
Reminder, once the PR becomes ready for a review, use |
My instinct is that that'd be really really bad for the common cases, since we'd end up with the loop in MIR even for the 2-param case, which then wouldn't fold down (mir-opt doesn't have loop optimizations) and thus wouldn't be inlinable and would thus be overall worse. To me by the time you're passing the N args separately, it's fine (maybe even good) to forcibly expand to N calls. (Said otherwise, I think of this as "you don't have to write |
556f726 to
68e07c4
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
68e07c4 to
24faa75
Compare
|
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
Correct, however it appears to be of little benefit in hindsight. I'm going to follow Scottmcm's suggestion and only split out a single value.
I did experiment with this on Godbolt a while ago and didn't really see any benefits. And the lack of a layout guarantee does make it a dubious optimisation. If it were valid, I'd expect to see it in the
Ahh that's much better. Thanks for the pointer! I've simplified the test to actually call into |
|
@rustbot ready |
Adjust implementation to reduce MIR generated.
24faa75 to
d0706de
Compare
|
Thanks for this! Makes me way more confident that we'll be able to migrate to these since even early we're already able to eliminate the indirections. @bors r+ |
…uwer Rollup of 14 pull requests Successful merges: - #162976 (fix quadratic naming of duplicate sidebar links) - #161275 (Refactor `core::cmp::{smallest, largest}` & add `mir-opt` test) - #163143 (cg_llvm: Use fewer FFI calls to check the target CPU's features) - #163188 (Adjust for Arm64EC name mangling when checking for exported symbols) - #163211 (`rustc_builtin_macros` cleanup, part 6) - #161386 (Don't merge distinct impl candidates) - #162942 (Remove `StashKey::AssociatedTypeSuggestion`) - #163096 (Don't suggest `std::` rustfix paths in `#![no_std]` crates) - #163110 (Mark `std::os::wasip2` with correct doc-cfgs, mark as unstable) - #163185 (properly decrement available_depth on cycles and provisional cache hits) - #163214 (revert r14 register names for arm) - #163226 (miri subtree update) - #163228 (Add regression test for trait predicate with escaping bounds) - #163234 (`rustc_dump_symbol_name`: add demangling information as a note instead)
Rollup merge of #161275 - bushrat011899:variadic_min_refactor, r=scottmcm Refactor `core::cmp::{smallest, largest}` & add `mir-opt` test Tracking Issue: #160728 # Description Based on [this](#160687 (comment)) feedback from the initial implementation, I've added a `mir-opt` test to highlight the amount of MIR added by using a variadic `min` implementation. ~~To my knowledge, there isn't a way to actually test the MIR generated by the actual implementation in `core`, so I've replicated a smaller representative sample in a test instead. Please see [GodBolt](https://godbolt.org/z/1qGzboeo3) for a more detailed comparison between the (unoptimised) MIR for the current and splatted versions of `min` (`max` omitted since it's identical).~~ ~~In short, the splat version does produce more MIR, especially for the additional trait implementations for larger tuple sizes. However, the added MIR almost exclusively comes from the indirection of calling a method on a trait, and not specifically the `#[rustc_splat]` variadic implementation. Because of this, it may be worth considering a MIR pass which could inline this kind of static dispatch.~~ Moving to a pre-codegen test with some MIR optimisation has narrowed the gap between `smallest` and `min` to basically nothing (an extra scope from the looks of it). I'm guessing the chained `a.min(b).min(c).min(d)` style (with a lack of explicit temporaries) is something MIR is able to readily optimise! From experimenting with this test, I also found a simpler way to implement `smaller` and `larger`. The most notable difference is instead of passing all arguments through the splat, I've pulled out the first argument so they aren't de/tupled. It improves codegen slightly, but more importantly simplifies the macro implementation. --- ## Notes - No AI tooling of any kind was used during the creation of this PR.
…uwer Rollup of 14 pull requests Successful merges: - rust-lang/rust#162976 (fix quadratic naming of duplicate sidebar links) - rust-lang/rust#161275 (Refactor `core::cmp::{smallest, largest}` & add `mir-opt` test) - rust-lang/rust#163143 (cg_llvm: Use fewer FFI calls to check the target CPU's features) - rust-lang/rust#163188 (Adjust for Arm64EC name mangling when checking for exported symbols) - rust-lang/rust#163211 (`rustc_builtin_macros` cleanup, part 6) - rust-lang/rust#161386 (Don't merge distinct impl candidates) - rust-lang/rust#162942 (Remove `StashKey::AssociatedTypeSuggestion`) - rust-lang/rust#163096 (Don't suggest `std::` rustfix paths in `#![no_std]` crates) - rust-lang/rust#163110 (Mark `std::os::wasip2` with correct doc-cfgs, mark as unstable) - rust-lang/rust#163185 (properly decrement available_depth on cycles and provisional cache hits) - rust-lang/rust#163214 (revert r14 register names for arm) - rust-lang/rust#163226 (miri subtree update) - rust-lang/rust#163228 (Add regression test for trait predicate with escaping bounds) - rust-lang/rust#163234 (`rustc_dump_symbol_name`: add demangling information as a note instead)
…r, r=scottmcm
Refactor `core::cmp::{smallest, largest}` & add `mir-opt` test
Tracking Issue: rust-lang#160728
# Description
Based on [this](rust-lang#160687 (comment)) feedback from the initial implementation, I've added a `mir-opt` test to highlight the amount of MIR added by using a variadic `min` implementation. ~~To my knowledge, there isn't a way to actually test the MIR generated by the actual implementation in `core`, so I've replicated a smaller representative sample in a test instead. Please see [GodBolt](https://godbolt.org/z/1qGzboeo3) for a more detailed comparison between the (unoptimised) MIR for the current and splatted versions of `min` (`max` omitted since it's identical).~~
~~In short, the splat version does produce more MIR, especially for the additional trait implementations for larger tuple sizes. However, the added MIR almost exclusively comes from the indirection of calling a method on a trait, and not specifically the `#[rustc_splat]` variadic implementation. Because of this, it may be worth considering a MIR pass which could inline this kind of static dispatch.~~
Moving to a pre-codegen test with some MIR optimisation has narrowed the gap between `smallest` and `min` to basically nothing (an extra scope from the looks of it). I'm guessing the chained `a.min(b).min(c).min(d)` style (with a lack of explicit temporaries) is something MIR is able to readily optimise!
From experimenting with this test, I also found a simpler way to implement `smaller` and `larger`. The most notable difference is instead of passing all arguments through the splat, I've pulled out the first argument so they aren't de/tupled. It improves codegen slightly, but more importantly simplifies the macro implementation.
---
## Notes
- No AI tooling of any kind was used during the creation of this PR.
…nathanBrouwer Rollup of 14 pull requests Successful merges: - rust-lang#162976 (fix quadratic naming of duplicate sidebar links) - rust-lang#161275 (Refactor `core::cmp::{smallest, largest}` & add `mir-opt` test) - rust-lang#163143 (cg_llvm: Use fewer FFI calls to check the target CPU's features) - rust-lang#163188 (Adjust for Arm64EC name mangling when checking for exported symbols) - rust-lang#163211 (`rustc_builtin_macros` cleanup, part 6) - rust-lang#161386 (Don't merge distinct impl candidates) - rust-lang#162942 (Remove `StashKey::AssociatedTypeSuggestion`) - rust-lang#163096 (Don't suggest `std::` rustfix paths in `#![no_std]` crates) - rust-lang#163110 (Mark `std::os::wasip2` with correct doc-cfgs, mark as unstable) - rust-lang#163185 (properly decrement available_depth on cycles and provisional cache hits) - rust-lang#163214 (revert r14 register names for arm) - rust-lang#163226 (miri subtree update) - rust-lang#163228 (Add regression test for trait predicate with escaping bounds) - rust-lang#163234 (`rustc_dump_symbol_name`: add demangling information as a note instead)
View all comments
Tracking Issue: #160728
Description
Based on this feedback from the initial implementation, I've added a
mir-opttest to highlight the amount of MIR added by using a variadicminimplementation.To my knowledge, there isn't a way to actually test the MIR generated by the actual implementation incore, so I've replicated a smaller representative sample in a test instead. Please see GodBolt for a more detailed comparison between the (unoptimised) MIR for the current and splatted versions ofmin(maxomitted since it's identical).In short, the splat version does produce more MIR, especially for the additional trait implementations for larger tuple sizes. However, the added MIR almost exclusively comes from the indirection of calling a method on a trait, and not specifically the#[rustc_splat]variadic implementation. Because of this, it may be worth considering a MIR pass which could inline this kind of static dispatch.Moving to a pre-codegen test with some MIR optimisation has narrowed the gap between
smallestandminto basically nothing (an extra scope from the looks of it). I'm guessing the chaineda.min(b).min(c).min(d)style (with a lack of explicit temporaries) is something MIR is able to readily optimise!From experimenting with this test, I also found a simpler way to implement
smallerandlarger. The most notable difference is instead of passing all arguments through the splat, I've pulled out the first argument so they aren't de/tupled. It improves codegen slightly, but more importantly simplifies the macro implementation.Notes