We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Performance and Data Layout step 15 of 20
#[inline], #[inline(always)], #[cold] and the codegen-unit trap
Almost every explanation of #[inline] says the same thing: “it only matters
across crate boundaries, because within a crate the compiler can already see
everything.” That is what the documentation implies and it is wrong, for a
reason nobody mentions.
The measurement
A small hash function called in a two-million-iteration loop, in a single
file, compiled with rustc -O:
| time | |
|---|---|
| no attribute | 0.604 ms |
#[inline] |
0.401 ms |
1.5×, inside one file. Now add -C codegen-units=1:
| time | |
|---|---|
| no attribute | 0.367 ms |
#[inline] |
0.372 ms |
The gap vanishes completely.
What is going on
rustc splits a crate into codegen units and hands them to LLVM in parallel. The release default is 16. LLVM optimises each unit independently, so a function in unit 7 is invisible to a caller in unit 3 — exactly the same situation as a cross-crate call, inside one file.
#[inline] marks a function so that its MIR is exported to every codegen
unit that calls it. That is the mechanism. It is not “please inline this”; it
is “make this available to inline”.
So the honest framing of this item, which you should carry away: this
benchmark measures a build-flag interaction as much as it measures a property
of your code. If anyone adds -C codegen-units=1 or LTO, the effect
disappears. This harness keeps rustc’s defaults, so it is real here.
Separately, on the same function, #[inline(never)] versus
#[inline(always)] measured 0.466 ms against 0.254 — 1.8×.
What to write
The hot loop is supplied by the grader. You write three helpers and put the right attribute on each.
pub fn mix(x: u64) -> u64 // called every iteration
pub fn fold(acc: u64, v: u64) -> u64 // called every iteration
pub fn report_overflow(i: usize) -> u64 // called ~1 iteration in 65 536
The bodies are given and already correct. #[inline] on the two hot ones,
#[cold] on the rare one. The starter has an #[inline] sitting on a
constant, which is a hard error — and a nice thing to have to think about,
because a const is already inlined at every use site, which is precisely why
the attribute is meaningless there.
(rustc 1.95 reports this as #[inline] attribute cannot be used on constants
with no error code at all. The error number that used to cover this family
has been retired, so rustc --explain will not help you. Read the message.)
The five forms and what they mean
| meaning | |
|---|---|
| (nothing) | inline within this codegen unit if LLVM feels like it |
#[inline] |
export the MIR everywhere; a hint, still LLVM’s decision |
#[inline(always)] |
a strong hint; still ignorable, and a pessimisation on a large function |
#[inline(never)] |
a strong hint the other way; a legitimate profiling tool |
#[cold] |
this function is rarely called: move it out of the hot path’s cache lines and predict the branch not-taken |
All of them are hints the compiler may ignore. None is a guarantee. And
#[inline(always)] on a big function makes things worse: it bloats the caller,
evicts instruction cache, and increases register pressure.
Some details that catch people out:
-
Inlining is not transitive.
ainliningbdoes not meancinlined intobis now ina. -
#[inline]cannot be combined with#[target_feature]. The Reference says so, and it is why SIMD helpers are shaped the way they are. -
#[inline]is ignored on#[no_mangle]functions. -
#[inline]is usually redundant on generic functions, because their MIR is already exported for monomorphisation. -
#[cold]on its own does not stop inlining. Pair it with#[inline(never)]when you want the body genuinely out of the way. -
core::hint::cold_path()(new stable in 1.95) marks a branch rather than a function as unlikely, which is often what you actually wanted.
#[inline(never)] as a diagnostic
This one is genuinely useful and rarely taught. Sampling profilers attribute
inlined frames to the caller, so a function that got inlined disappears
from your flamegraph and its cost silently lands on whoever called it. Putting
#[inline(never)] on a suspect function for one profiling run makes it
reappear as its own frame. Take it off afterwards.
Related lints
inline_fn_without_body (an #[inline] on a trait method declaration, which
has no body to inline — default-on), inline_always (pedantic; argues you
should almost never reach for it), missing_inline_in_public_items (restriction
— the opposite opinion, for library crates that want everything inlinable
downstream).
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.