[perf] Allow cross-crate inlining through explicitly inline trait calls - #159119
[perf] Allow cross-crate inlining through explicitly inline trait calls#159119obi1kenobi wants to merge 1 commit into
Conversation
|
@bors try @rust-timer queue |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
[perf] Enable inlining trait items that only contain inlined code.
This comment has been minimized.
This comment has been minimized.
|
Finished benchmarking commit (20bd965): comparison URL. Overall result: ββ regressions and improvements - please read:Benchmarking means the PR may be perf-sensitive. It's automatically marked not fit for rolling up. Overriding is possible but disadvised: it risks changing compiler perf. Next, please: If you can, justify the regressions found in this try perf run in writing along with @bors rollup=never Instruction countOur most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.
Max RSS (memory usage)Results (primary 0.9%, secondary 1.1%)A less reliable metric. May be of interest, but not used to determine the overall result above.
CyclesResults (primary -7.0%, secondary 5.4%)A less reliable metric. May be of interest, but not used to determine the overall result above.
Binary sizeResults (primary -0.0%, secondary 0.1%)A less reliable metric. May be of interest, but not used to determine the overall result above.
Bootstrap: 490.729s -> 491.057s (0.07%) |
|
Looks like this does indeed help specifically |
75fddd9 to
8ba0090
Compare
|
@bors try @rust-timer queue |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
[perf] Enable inlining trait items that only contain inlined code.
| match codegen_fn_attrs.inline { | ||
| InlineAttr::Always | InlineAttr::Force { .. } => true, | ||
| InlineAttr::Hint => !matches!(tcx.sess.opts.optimize, OptLevel::No), | ||
| InlineAttr::None | InlineAttr::Never => false, | ||
| } |
There was a problem hiding this comment.
This is probably worth considering more closely. Should we keep the behavior the same in both optimized and unoptimized builds?
If yes, I'm happy to change this.
If no, then perhaps we should consider not bothering to resolve the trait fn call in unoptimized builds, and instead add an early-exit for that case near the top of this function.
Feedback welcome!
There was a problem hiding this comment.
hm. Ideally we'd keep it entirely in sync with what cross_crate_inlineable does. We've seen very nice compile-time improvements in debug mode due to inlining.
let's do a perf run with this logic entirely deduplicated with cross_crate_inlineable, (and done at the top both of this function and cross_crate_inlineable
|
Some changes occurred to MIR optimizations cc @rust-lang/wg-mir-opt |
This comment has been minimized.
This comment has been minimized.
|
Finished benchmarking commit (b146532): comparison URL. Overall result: ββ regressions and improvements - please read:Benchmarking means the PR may be perf-sensitive. It's automatically marked not fit for rolling up. Overriding is possible but disadvised: it risks changing compiler perf. Next, please: If you can, justify the regressions found in this try perf run in writing along with @bors rollup=never rustc-perf Instruction countOur most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.
Max RSS (memory usage)Results (primary 1.0%, secondary -0.2%)A less reliable metric. May be of interest, but not used to determine the overall result above.
CyclesResults (primary -0.7%, secondary -0.5%)A less reliable metric. May be of interest, but not used to determine the overall result above.
Binary sizeResults (primary -0.1%, secondary -0.5%)A less reliable metric. May be of interest, but not used to determine the overall result above.
Bootstrap: 491.898s -> 491.326s (-0.12%) |
| // if we haven't already given up (or are about to give up) | ||
| // on cross-crate inlining due to other disqualifying findings. | ||
| if self.cost_allows_cross_crate_inlining() | ||
| && !matches!(unwind, UnwindAction::Cleanup(_)) |
There was a problem hiding this comment.
document why we don't do this for calls which can unwind
| return false; | ||
| }; | ||
|
|
||
| let InstanceKind::Item(_) = instance.def else { |
There was a problem hiding this comment.
what else can it be if it's a trait method and got successfully resolved to an actual instance?
|
|
||
| let typing_env = self.typing_env(); | ||
| let Some((instance, _)) = | ||
| inline::try_resolve_call_instance(tcx, typing_env, def_id, ty::Binder::dummy(args)) |
There was a problem hiding this comment.
cc @addiesh this is unreachable under your work because the const_fn_def call changed in visit_terminator below will ICE. Unless... this late in the MIR pipeline, do we even have any late bound vars left? it's not like they matter anymore and already all got resolved properly in borrowck
| match codegen_fn_attrs.inline { | ||
| InlineAttr::Always | InlineAttr::Force { .. } => true, | ||
| InlineAttr::Hint => !matches!(tcx.sess.opts.optimize, OptLevel::No), | ||
| InlineAttr::None | InlineAttr::Never => false, | ||
| } |
There was a problem hiding this comment.
hm. Ideally we'd keep it entirely in sync with what cross_crate_inlineable does. We've seen very nice compile-time improvements in debug mode due to inlining.
let's do a perf run with this logic entirely deduplicated with cross_crate_inlineable, (and done at the top both of this function and cross_crate_inlineable
| checker.calls == 0 | ||
| && checker.resumes == 0 | ||
| && checker.landing_pads == 0 | ||
| && checker.statements <= threshold | ||
|
|
||
| checker.cost_allows_cross_crate_inlining() |
There was a problem hiding this comment.
Make this a separate commit before the other changes in this PR
| @@ -0,0 +1,74 @@ | |||
| //@ compile-flags: -Copt-level=3 -Zinline-mir=no -Zcross-crate-inline-threshold=100 | |||
There was a problem hiding this comment.
I don't think it is a good idea to pin down the threshold in a test. You might think that makes the test less flaky, but actually it means we change from testing how rustc in the wild optimizes code to whether this specific heuristic works as intended, in its own private universe.
View all comments
Inferred cross-crate inlining currently makes an otherwise-unannotated function available to downstream crates only when its optimized MIR is small and contains no ordinary calls, with intrinsic calls exempted.
This misses small functions whose remaining calls are statically dispatched trait calls to implementations explicitly marked
#[inline], since the MIR call identifies the trait item but the relevant inline attribute belongs to the implementation selected for that call.This PR addresses that edge case by resolving the selected implementation during cross-crate inlining cost analysis. When resolution produces a statically selected, explicitly inline item that is not externally exported, the call no longer disqualifies the enclosing function.
Motivation
The
nom-jsonruntime benchmark uses nom 7.1.3, and its hot path includes an unannotated implementation equivalent to:The iterator operations here are marked
#[inline], but prior to this PR that wasn't sufficient βfind_token()was not made available for downstream cross-crate inlining.The benchmarked function uses the JSON parser and repeatedly uses
find_token()to look for". Makingfind_token()available for downstream inlining allows constant propagation to replace the general-case search with a direct comparison, which is much more efficient.The same optimization already happens in LTO-enabled builds. However, LTO is much more expensive than regular
cargo build --release. This PR recovers that optimization for regularcargo build --releasebuilds.The same optimization can also be recovered by marking
find_token()with the#[inline]attribute. While that's still probably a reasonable idea, I still think it's a good idea to offer better out-of-the-box performance for default (non-LTO) release builds even when users haven't realized that their small cross-crate trait function implementation is a good#[inline]candidate.r? oli-obk
AI disclosure: The optimization opportunity here was discovered as part of a systematic probe for missed optimizations using a combination of both traditional and AI tools. The code here was initially prototyped and vetted by AI tools, followed by additional manual work. I secured approval in advance from the reviewer I pinged. I stand behind the quality of the code I'm submitting, and I vouch it's as good or better compared to if I had written every line by my own hand.