Trait Method inlining


#1

This is related to the closure inlining topic, but sufficiently different that I opted for a new thread.

Consider the following code:

trait Foo {
  // #[inline(always)]
  fn foo(&self);
}

type Bar;

impl Foo for Bar {
  // #[inline(always)]
  fn foo(&self) {
    // code here
  }
}

There are 2 places I could add an #[inline(always)] attribute, both slashed out here.
Which of these tells rustc that the method foo is to always be inlined?
And what does the attr do in the other place?

My confusion springs forth from 2 potential, but mutually exclusive scenarios:

  1. The attr in the trait definition could have the semantics “all impls of this method are to be inlined”, while the attr on the method impl would have the semantics “this specific impl of this method is to be inlined”
  2. Only 1 site has semantics attached, and would thus likely be the impl site rather than the trait definition.

Which of these 2 scenarios is true?


#2

It affects only the method in question - it’s not a request for all impls to be inlined (that would be very bad if that were true).

In this case, adding inline hint to the trait method is a nop because the method has no body. But, suppose you did have a body and then implemented Foo for some type that didn’t “override” foo. In that case, once the compiler statically sees the call target being foo from the trait, the inlining hint will play a role.

Vast majority of the time you’ll apply the inlining attribute to impls as that’s what the compiler actually sees in static dispatch scenarios. The only case where you may want to add the hint to the trait method is like the mentioned case: majority of impls just inherit the default from the trait.


#3

So if I understand you correctly:
The attribute effectively operates on a fn/method body and thus is only effective there.
In all other places (e.g. the trait definition) it becomes a NOP.

Does that sum it up nicely?


#4

Yeah, you need a body otherwise there’s nothing to inline :slight_smile:


#5

Given the semantics you outlined that makes perfect sense :slight_smile:

But I would not have been surprised if there had been a version of the attribute that does operate on all method impls at once. How I see it is that effectively it would have become part of the contract defined by the trait. Obviously not useful in all or even a majority of cases, but I can see it being useful sometimes.


#6

Can you give an example of where such a thing would be useful?


#7

It might be useful when you want something akin to a 100% 0-overhead trait, including all methods. This in turn might be useful when doing aggressive inlining, as I am currently doing in a parser I’m currently rewriting for speed.

Not that I need this per se, the current capabilities work just fine.

It’s mostly a matter of defaults, since it’s always possible to put an attr on every fn, method and closure that is important. If the present-day capability is the scalpel, the first paragraph would be discussing the chainsaw.


#8

The topic has inspired me to put #[inline(never)] on the entry (after a simple closure) of a threaded function call. Giving something nicer in stack traces.

Note on the topic is; when the calls are indirect there won’t be inlining.

Inlining speed increase/decrease is dependent on target CPU/Platform/Compiler. If your going to the trouble of inlining then you probably also should be adding PGO. (Also probably keeping benchmarks for later retest use when code changes.)


#9

This might work only if you intend to be the sole implementer of the trait: it’s a private crate/module level trait and you own all the impls and know a priori that they all benefit from forced inlining. Otherwise, this directive really belongs where the code blob is as that’s where you have the most context and, when profiling, can know which call targets need the tweak. I find this use case very very niche :slight_smile:.