Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When is a method eligible to be inlined by the CLR?

I've observed a lot of "stack-introspective" code in applications, which often implicitly rely on their containing methods not being inlined for their correctness. Such methods commonly involve calls to:

  • MethodBase.GetCurrentMethod
  • Assembly.GetCallingAssembly
  • Assembly.GetExecutingAssembly

Now, I find the information surrounding these methods to be very confusing. I've heard that the run-time will not inline a method that calls GetCurrentMethod, but I can't find any documentation to that effect. I've seen posts on StackOverflow on several occasions, such as this one, indicating the CLR does not inline cross-assembly calls, but the GetCallingAssembly documentation strongly indicates otherwise.

There's also the much-maligned [MethodImpl(MethodImplOptions.NoInlining)], but I am unsure if the CLR considers this to be a "request" or a "command."

Note that I am asking about inlining eligibility from the standpoint of contract, not about when current implementations of the JITter decline to consider methods because of implementation difficulties, or about when the JITter finally ends up choosing to inline an eligible method after assessing the trade-offs. I have read this and this, but they seem to be more focused on the last two points (there are passing mentions of MethodImpOptions.NoInlining and "exotic IL instructions", but these seem to be presented as heuristics rather than as obligations).

When is the CLR allowed to inline?

like image 775
Ani Avatar asked Jan 11 '11 16:01

Ani


4 Answers

It is a jitter implementation detail, the x86 and x64 jitters have subtly different rules. This is casually documented in blog posts of team members that worked on the jitter but the teams certainly reserve the right to alter the rules. Looks like you already found them.

Inlining methods from other assemblies is most certainly supported, a lot of the .NET classes would work quite miserably if that wasn't the case. You can see it at work when you look at the machine code generated for Console.WriteLine(), it often gets inlined when you pass a simple string. To see this for yourself, you need to switch to the Release build and change a debugger option. Tools + Options, Debugging, General, untick "Suppress JIT optimization on module load".

There is otherwise no good reason to consider MethodImpOptions.NoInlining maligned, it's pretty much why it exists in the first place. It is in fact used intentionally in the .NET framework on lots of small public methods that call an internal helper method. It makes exception stack traces easier to diagnose.

like image 194
Hans Passant Avatar answered Oct 09 '22 00:10

Hans Passant


Hans Passant's answer notwithstanding, here first a couple of hints as of 2004, and further down some more up to date information. They are subject to change, but they do give you an idea on what to look for if you want to make a method eligible for inlining:

the JIT won’t inline:

  • Methods marked with MethodImplOptions.NoInlining
  • Methods larger than 32 bytes of IL
  • Virtual methods
  • Methods that take a large value type as a parameter
  • Methods on MarshalByRef classes
  • Methods with complicated flowgraphs
  • Methods meeting other, more exotic criteria

In particular, there is MethodImplOptions.AggressiveInlining, which is supposed to lift the 32 bytes limit (or whatever it happens to be these days and for your platform).

.Net 3.5 added heuristics that help it determine whether To Inline or not to Inline, which is probably a good thing, although it makes it harder for the developer to predict the jitter's decision:

A quote from the article:

  1. If inlining makes code smaller then the call it replaces, it is ALWAYS good. Note that we are talking about the NATIVE code size, not the IL code size (which can be quite different).

  2. The more a particular call site is executed, the more it will benefit from inlning. Thus code in loops deserves to be inlined more than code that is not in loops.

  3. If inlining exposes important optimizations, then inlining is more desirable. In particular methods with value types arguments benefit more than normal because of optimizations like this and thus having a bias to inline these methods is good.

Thus the heuristic the X86 JIT compiler uses is, given an inline candidate.

  1. Estimate the size of the call site if the method were not inlined.

  2. Estimate the size of the call site if it were inlined (this is an estimate based on the IL, we employ a simple state machine (Markov Model), created using lots of real data to form this estimator logic)

  3. Compute a multiplier. By default it is 1

  4. Increase the multiplier if the code is in a loop (the current heuristic bumps it to 5 in a loop)

  5. Increase the multiplier if it looks like struct optimizations will kick in.

  6. If InlineSize <= NonInlineSize * Multiplier do the inlining.

like image 22
Evgeniy Berezovsky Avatar answered Oct 09 '22 02:10

Evgeniy Berezovsky


While Hans' answer is correct, there is one omission, not necessarily about when a method is eligible for inlining, but when a method is not.

Abstract and virtual methods are not eligible for inlining in the CLR.

It's important to note as it whittles down the conditions under which a method may be inlined.

like image 32
casperOne Avatar answered Oct 09 '22 02:10

casperOne


There's more information on inlining of MethodBase.GetCurrentMethod on this thread http://prdlxvm0001.codify.net/pipermail/ozdotnet/2011-March/009085.html

Paraphrasing heavily, it states that the RefCrawlMark does NOT stop the calling method being inlined. However, RequireSecObject does have the side affect of stopping the caller being inlined.

In addition, the Assembly.GetCallingAssembly and Assembly.GetExecutingAssembly methods do NOT have this attribute.

like image 27
stew Avatar answered Oct 09 '22 00:10

stew