in Development

Inline Methods

Method inlining is an optimization technique that consists of putting the body of a method inside the body of all the caller methods and removing the original method. Let’s suppose the next sample:

class Demo
    public void Talk()
    private void SayHello()

To inline the method SayHello, we should refactor our code in the next way:

class Demo
    public void Talk()

As you can appreciate we have introduced “Console.WriteLine(“Hello!”);” directly inside the method Talk.

This technique provides some performance benefits by reducing the overhead associated to a new method call. There are many characteristics that make a method a good candidate to be inlined like: reduced size and complexity, methods frequently used, etc. Fortunately this is not something we should think ourselves, most compilers include this optimization when compile our code.

.NET Framework is not an exception and although it does not have an explicit “inline” clause to suggest its use, as you can find in C++, the JIT (Just in Time) Compiler performs inline optimizations when it considers that can help to improve the performance.

If we debug the first version of the code we can appreciate how the method “Talk” and “SayHello” are inlined. The code must be compiled in Release mode and run out of Visual Studio in order the jitter applies the optimizations, so a possible way you can see it is debugging with WinDBG. To do it just run the code and attach the executable, load the Son of Strike extension and run the command

!EEStack -EE

this will display something similar to:

0015ee28 793e8e33 (MethodDesc 0x79259c00 +0×7   System.Console.ReadKey()) 
0015ee2c 009100ea (MethodDesc 0x3030a0   +0×32  Demo.WaitUserAction()) 
0015ee3c 009100a7 (MethodDesc 0×303028   +0×37  Program.Main(System.String[]))

As we see the stack does not display the methods “Talk” and “SayHello” and if we run the command

!DumpMT -MD [address of method table]

we will see how SOS does not reflect the methods as jitted even if we know the code has been executed. This is because, after inline those methods, its body has been expanded within the Main method but they do “not exist” at all.

79371278   7914b928   PreJIT System.Object.ToString() 
7936b3b0   7914b930   PreJIT System.Object.Equals(System.Object) 
7936b3d0   7914b948   PreJIT System.Object.GetHashCode() 
793624d0   7914b950   PreJIT System.Object.Finalize() 
0030c035   00303090   NONE Demo.Talk() 
0030c039   00303098   NONE Demo.SayHello()  
009100b8   003030a0   JIT Demo.WaitUserAction() 
0030c041   003030a8   NONE Demo..ctor()

Just for fun we can change the code and force the jitter to do not inline our methods. This is accomplished adding the attribute [MethodImpl(MethodImplOptions.NoInlining)] on top of the method definition for “Talk” and “SayHello”. If we recompile and debug with WinDBG again we will see the differences.

Now the stack shows the calls to the methods “Talk” and “SayHello” and both methods appear as jitted.

0021ed10 793e8e33 (MethodDesc 0x79259c00 +0×7 System.Console.ReadKey()) 
0021ed14 009a0132 (MethodDesc 0x8830a0 +0×32 Demo.WaitUserAction()) 
0021ed24 009a00ed (MethodDesc 0×883098 +0x2d Demo.SayHello()) 
0021ed28 009a00a6 (MethodDesc 0×883090 +0×6 Demo.Talk())  
0021ed2c 009a0084 (MethodDesc 0×883028 +0×14 Program.Main(System.String[]))

79371278 7914b928 PreJIT System.Object.ToString() 
7936b3b0 7914b930 PreJIT System.Object.Equals(System.Object) 
7936b3d0 7914b948 PreJIT System.Object.GetHashCode() 
793624d0 7914b950 PreJIT System.Object.Finalize() 
009a00a0 00883090 JIT Demo.Talk() 
009a00c0 00883098 JIT Demo.SayHello() 
009a0100 008830a0 JIT Demo.WaitUserAction() 
0088c041 008830a8 NONE Demo..ctor()

You can go one step further checking not only the stacks, but also how the code produced contains the inline instructions. If we use the command !u within WindDBG for each method “Main”, “Talk” and “SayHello” when they are not inlined we obtain something similar to this:

Begin 009a0070, size 15 
009a0070 b9b0308800 mov ecx,8830B0h (MT: Demo) 
009a0075 e8a21f77ff call 0011201c (JitHelp: CORINFO_HELP_NEWSFAST) 
009a007a 8bc8 mov ecx,eax 
009a007c 3909 cmp dword ptr [ecx],ecx 
009a007e ff15e8308800 call dword ptr ds:[8830E8h] (Demo.Talk(), mdToken: 06000003) 
009a0084 c3 ret 

Begin 009a00a0, size 7 
009a00a0 ff15ec308800 call dword ptr ds:[8830ECh] (Demo.SayHello(), mdToken: 06000004) 
009a00a6 c3 ret

Begin 002500c0, size 2e 
002500c0 833d8c10f20200 cmp dword ptr ds:[2F2108Ch],0 
002500c7 750a jne 002500d3 
002500c9 b901000000 mov ecx,1 
002500ce e891581179 call mscorlib_ni+0x2a5964 (79365964) (System.Console.InitializeStdOutError(Boolean), mdToken: 06000770) 
002500d3 8b0d8c10f202 mov ecx,dword ptr ds:[2F2108Ch] (Object: System.IO.TextWriter+SyncTextWriter) 
002500d9 8b153c30f202 mov edx,dword ptr ds:[2F2303Ch] (“Hello!”) 
002500df 8b01 mov eax,dword ptr [ecx] 
002500e1 ff90d8000000 call dword ptr [eax+0D8h] 
002500e7 ff15f0301b00 call dword ptr ds:[1B30F0h] (Demo.WaitUserAction(), mdToken: 06000005) 
002500ed c3 ret

When the methods are inlined we cannot run anymore the command !u for them, but if we do it for the Main method we can see without being an expert on native code how the code contains now the SayHello instructions.

Begin 00970070, size 38 
00970070 b9b0303a00 mov ecx,3A30B0h (MT: Demo) 
00970075 e8a21fa2ff call 0039201c (JitHelp: CORINFO_HELP_NEWSFAST) 
0097007a 833d8c10c80200 cmp dword ptr ds:[2C8108Ch],0 
00970081 750a jne 0097008d 
00970083 b901000000 mov ecx,1 
00970088 e8d7589f78 call mscorlib_ni+0x2a5964 (79365964) (System. Console.InitializeStdOutError(Boolean), mdToken: 06000770) 
0097008d 8b0d8c10c802 mov ecx,dword ptr ds:[2C8108Ch] (Object: System. IO.TextWriter+SyncTextWriter) 
00970093 8b153c30c802 mov edx,dword ptr ds:[2C8303Ch] (“Hello!”) 
00970099 8b01 mov eax,dword ptr [ecx] 
0097009b ff90d8000000 call dword ptr [eax+0D8h] 
009700a1 ff15f0303a00 call dword ptr ds:[3A30F0h] (Demo.WaitUserAction(), mdToken: 06000005) 
009700a7 c3 ret

As you can see there are many things to explore and get fun with WinDBG.