Inlining is an important optimization that allows compilers to eliminate the cost of method calls in situations where the method call overhead is more significant than the method body itself. The CLR JIT uses inlining conservatively, but features some nice tricks such as interface method call inlining – this was one of the first things I covered on this blog, almost five years ago.
The limitations on JIT inlining are not known precisely, but some criteria have been announced previously (in 2004!). Namely, 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
Today, however, I’d like to direct your attention towards a new flag in CLR 4.5, MethodImplOptions.AggressiveInlining. The documentation here is very brief:
The method should be inlined if possible.
Well, thanks. Interestingly, Mono introduced support for this option as well, committed on January 5th (two weeks ago!), and here’s the effect it has on deciding whether to inline a method, inside the function mono_method_check_inlining:
- if (header.code_size >= inline_limit)
+ if (header.code_size >= inline_limit && !(method->iflags & METHOD_IMPL_ATTRIBUTE_AGGRESSIVE_INLINING))
(The inline_limit parameter is configurable by an environment variable, and defaults to 20.)
So, what is the effect of AggressiveInlining on the Microsoft JIT?
From what I checked, it seems to be similar to what it does in Mono. Namely, methods that are not inlined only because of code size are inlined when this attribute is applied to them.
Here are two methods that you can try yourself:
public static int SmallMethod(int i, int j)
{
if (i > j)
return i + j;
else
return i + 2 * j – 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int LargeMethod(int i, int j)
{
if (i + 14 > j)
{
return i + j;
}
else if (j * 12 < i)
{
return 42 + i – j * 7;
}
else
{
return i % 14 – j;
}
}
The code size for these methods is 16 and 34, respectively. Without the AggressiveInlining attribute, the first method is inlined and the second is not inlined. With the AggressiveInlining attribute, the second method is inlined as well.
However, methods that couldn’t be inlined previously because of other criteria are still not inlined. I checked the following, and neither of these methods was inlined:
- Recursive method
- Virtual method (even if the static type of the receiver variable is sealed)
- Method with exception handling (representing a “complicated flowgraph”)
I am posting short updates and links on Twitter as well as on this blog. You can follow me: @goldshtn
Is a simple “throw” of an exception considered a “complicated flowgraph”?
AFAIK a throw is not considered a complicated flowgraph, and will not prevent inlining.
Sasha, it seems to me that throwing an exception affects JIT possibility to inlining a method. I wrote a very simple method that only throws an exception of type Exception. And it was’t inlined. Even [MethodImpl(MethodImplOptions.AggressiveInlining)] attribute didn’t help. It is JIT-x86 that behaves this way. You may encounter another behaviour considering x64-JIT or RyuJIT. Can you please shed some light?
What’s the exact method body you’re trying to inline? How are you testing whether it was or was not inlined?
Sasha,
The method body is:
public class A
{ public static bool Method(char c)
{
throw new Exception();
}
}
I invoke this method like this:
bool x = A.Method(‘c’);
System.Console.WriteLine(x);
I use Visual Studio Disassembly window to investigate jitted assembly code. And it looks like the following:
push ebp
mov ebp,esp
push esi
mov ecx,63h
call dword ptr ds:[002A3860h]
mov esi,eax
Thus, it seems to me that there is no inlining.
Indeed, I was able to reproduce your results. It is the same with the x64 JIT as well. Perhaps the criteria for inlining changed.
Pingback: The Art of Benchmarking (Updated 2014-09-23) | Matt on Software