Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ILGenerator method inlining

Given following code:

using System;
using System.Reflection.Emit;
using System.Diagnostics;
using System.Reflection;

namespace ConsoleApplication1
{
    class A
    {
        public int Do(int n)
        {
            return n;
        }
    }

    public delegate int DoDelegate();

    class Program
    {
        public static void Main(string[] args)
        {
            A a = new A();

            Stopwatch stopwatch = Stopwatch.StartNew();
            int s = 0;
            for (int i = 0; i < 100000000; i++)
            {
                s += a.Do(i);
            }

            Console.WriteLine(stopwatch.ElapsedMilliseconds);
            Console.WriteLine(s);


            DynamicMethod dm = new DynamicMethod("Echo", typeof(int), new Type[] { typeof(int) }, true);
            ILGenerator il = dm.GetILGenerator();

            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ret);

            DynamicMethod dm2 = new DynamicMethod("Test", typeof(int), new Type[0]);
            il = dm2.GetILGenerator();


            Label loopStart = il.DefineLabel();
            Label loopCond = il.DefineLabel();

            il.DeclareLocal(typeof(int));   // i
            il.DeclareLocal(typeof(int));   // s

            // s = 0;
            il.Emit(OpCodes.Ldc_I4_0);
            il.Emit(OpCodes.Stloc_1);

            // i = 0;
            il.Emit(OpCodes.Ldc_I4_0);
            il.Emit(OpCodes.Stloc_0);

            il.Emit(OpCodes.Br_S, loopCond);

            il.MarkLabel(loopStart);

            // s += Echo(i);
            il.Emit(OpCodes.Ldloc_1);   // Load s
            il.Emit(OpCodes.Ldloc_0);   // Load i
            il.Emit(OpCodes.Call, dm);  // Call echo method
            il.Emit(OpCodes.Add);
            il.Emit(OpCodes.Stloc_1);

            // i++
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Ldc_I4_1);
            il.Emit(OpCodes.Add);
            il.Emit(OpCodes.Stloc_0);

            il.MarkLabel(loopCond);

            // Check for loop condition
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Ldc_I4, 100000000);
            il.Emit(OpCodes.Blt_S, loopStart);

            il.Emit(OpCodes.Ldloc_1);
            il.Emit(OpCodes.Ret);


            DoDelegate doDel = (DoDelegate)dm2.CreateDelegate(typeof(DoDelegate));
            s = doDel.Invoke();     // Dummy run to force JIT


            stopwatch = Stopwatch.StartNew();
            s = doDel.Invoke();
            Console.WriteLine(stopwatch.ElapsedMilliseconds);
            Console.WriteLine(s);
        }
    }
}

Call to method Do gets inlined. Loop finishes in about 40 ms. If I, for example, make Do to be virtual function, it doesn't get inlined, and the loop finishes in 240 ms. So far so good. When I use ILGenerator to generate Do method (Echo), and then generate DynamicMethod with the same loop as the given main method, call to Echo method never gets inlined, and it takes about 240 ms for a loop to finish. MSIL code is correct since it returns the same result as the C# code. I was sure that method inlining is something that is done by the JIT, so I see no reason for it not to inline the Echo method.

Does anybody know why this simple method wont get inlined by the JIT.

like image 813
user102808 Avatar asked Dec 31 '11 00:12

user102808


1 Answers

After further investigation I have concluded following:

  1. ILGenerator generated methods will never get inlined. It doesn't matter whether you call them using delegate, from another DynamicMethod or from a method created with MethodBuilder.
  2. Existing methods (the ones coded in C# and compiled by VS) can get inlined only when called from a method created with MethodBuilder. If called from DynamicMethod, they will never get inlined.

I have concluded this after thoroughly testing many samples, and looking into final assembler code.

like image 177
user102808 Avatar answered Nov 02 '22 14:11

user102808