Hello Rustaceans,please forgive my poor English. I want to ask: under what circumstances should inline be used? My humble opinion is that it makes sense when a function is called very frequently or when the function’s logic is very small (this can reduce the overhead and number of function calls).
Following this idea, when I was reading core/slice/rotate.rs, I noticed this function:
// FIXME(const-hack): Use cmp::min when available in const
const fn const_min(left: usize, right: usize) -> usize {
if right < left { right } else { left }
}
It is called many times and is very small, but why isn’t it marked as inline?
When a large function is called often, inlining would increase the size of the executable, as it it duplicated for every call. So inlining makes most sense for small functions, or for a large one that is called only once. Most modern compilers can do inlining on their own, so the inline attribute might be not needed. And some functions, like recursive ones, can never be inlined.
Rust documentation states that the compiler does inlining automatically, so I usually do not concern about it. It can be more interesting when you explicitly say - do not inline.
Be careful about what “often” or “frequently” means here. If a function foo is large and the program contains many different call sites to foo, that is,
fn main() {
foo(); // foo is a poor candidate for inlining
bar();
foo();
baz();
foo();
}
then foo may be a poor candidate for inlining, because there is likely little benefit in exchange for the code size increase. If there is only one call site but they are executed often,
fn main() {
for i in 0..1_000_000 {
foo(i);
}
}
then inlining it does not necessarily increase code size, and may enable further optimization related to the loop.
In most cases, any “very small” function will automatically be considered for inlining and you do not need any #[inline] attribute. The noteworthy exception is if:
the function call and the function definition are in different crates, and
the function definition contains a lot of Rust code but compiles to small machine code (such as some kinds of an enum matches).
In general, if you are considering spending time on #[inline] hint attributes, you should write benchmarks and use them to confirm whether the annotations make any difference at all. A profiler can also be useful to see which functions aren't inlined but are called frequently, that therefore may be worth trying inline attributes on.
Something to keep in mind is that the overhead of function calls themselves is often not the important part. The much more powerful effect of inlining is that, once inlined, the combination of the caller and the callee can be optimized as a single piece of code. For example, if a function call foo(false) is inlined, then the compiler can optimize the code in foo knowing that the argument is always false, completely eliminating many branches.
Inlining a function that does not have any such potential for optimization after inlining may show little benefit, or even perform worse due to code size increase (increasing instruction cache pressure).
What makes it even more complicated is link time optimization (LTO) or dynamic linking. I think LTO enables automatic inlining over crates boundaries, and I assume that function calls for dynamically linked libraries would not be inlined?