Is it possible to increase C# AOT inline method length limit?

When I force the C# AOT compiler with MethodImpl(MethodImplOptions.AggressiveInlining) to do a really long method:

enter image description here

At the very end, it stops inlining and starts calling every possible method (even simple getters):

enter image description here

Is there a way to prevent it from stopping the inlining? Is there some method inline limit that can be increased?

EDIT: I was hoping it wouldn’t devolve into a “you shouldn’t do that” response. Why I can’t just ask the question without justifying? So, here is the justifying. My application is meant to be time performance oriented. I don’t care if the compiled code is 10000x bigger. What matters to me is time performance and I can measure about 20% better time performance if this code is inlined.

EDIT2: I’ve made some reasearch and it seems that there exists configuration knobs for JIT https://github.com/steveharter/dotnet_coreclr/blob/master/Documentation/project-docs/clr-configuration-knobs.md
Specifically there is a JITInlineSize knob. I’ve tried to set its value via ENV variable, but it doesn’t help.

EDIT3: I’ve made a reproducible example. Please see https://github.com/dagid4/AotInlineLimitProof

  • “Why I can’t just ask the question without justifying?” – in the 14 years I’ve been here on StackOverflow, this is the first time I’ve seen anyone use IDA to look at JIT’d .NET code – I’m impressed (though I’m more of a Ghidra guy myself) – but I also think it’s silly, because the CLR could be updated at any time with changes that result in radically different native codegen, jus’ sayin’

    – 




  • “My application is meant to be time performance oriented”then don’t use .NET: C#/.NET is a general-purpose applications-programming-language meant for getting shit done, no-one ever said that C#/.NET (with its JIT delays and GC pauses) was ever suitable for high-performance applications. There’s a reason that HPC programs are still written in FORTRAN.

    – 




  • “At the very end, it stops inlining and starts calling every possible method (even simple getters):”) – are those virtual properties – or invoked via generic code? If so, then non-inlining is likely unavoidable.

    – 




  • Also, show us your actual code, not just unreadable screenshots from IDA

    – 

  • The aggresive inlining actually works pretty well to some limit. But when the limit is reached, it stops inlining. I can show you my code, but it doesn’t matter. Just create a long method (in terms of instructions) and you will see.

    – 




Pretty sure that’s not how that works.

The MethodImplAttribute applies to your method, and AggressiveInlining tells the JIT (and thus AOT) that it should inline your method if possible. If your method is too large – based apparently on the number of bytes it compiles to – then it will be called normally rather than inlined.

Inlining can save a few cycles per invocation, and will happen automatically for trivial methods when optimization is enabled. AggressiveInlining raises the threshold for inlining but doesn’t guarantee that it will happen, or that the output code will be any better.

Here’s a direct quote from the docs:

Unnecessary use of this attribute can reduce performance. The attribute might cause implementation limits to be encountered that will result in slower generated code.

And that’s what’s happening here.

You’ve told the compiler that it should do what it can to inline your “really long” (whatever that means) method, and it does its best to comply. Your method apparently calls a bunch of other methods that would normally be inlined, but doing so will make your method exceed the inlining limit. Since you’ve insisted that it be inlined, and inlining those other methods would prevent that, the compiler is balancing the books in favor of what you told it to do. If inlining those ‘simple getter’ calls would make your method too large to inline, they don’t get inlined.

AggressiveInlining should be used rarely if at all. If you look at the runtime source you’ll find that the vast majority of cases where the standard libraries use it, it’s a performance tweak for commonly-called methods that would be inlined under normal circumstances anyway.

For example, AsyncTaskMethodBuilder has this:

public Task Task
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    get => m_task ?? InitializeTaskAsPromise();
}

That’s a trivial getter that would normally be inlined, except in cases like yours where the JIT is already running low on space. It’s important that accessing the Task be as quick as possible though, thus the attribute. It should take priority over optimizations because it’s a definite win to inline this.

What it’s not intended for is what you’re trying to do: force the JIT to duplicate large chunks of your code inline.

Leave a Comment