ILGenerator方法内联

时间:2011-12-31 00:05:24

标签: .net methods inline cil ilgenerator

给出以下代码:

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);
        }
    }
}

调用方法Do内联。循环在大约40毫秒内完成。例如,如果我将Do设为虚函数,它就不会内联,并且循环在240 ms内完成。到现在为止还挺好。当我使用ILGenerator生成Do方法(Echo),然后使用与给定main方法相同的循环生成DynamicMethod时,调用Echo方法永远不会内联,并且循环完成需要大约240 ms。 MSIL代码是正确的,因为它返回与C#代码相同的结果。我确信方法内联是由JIT完成的,所以我认为没有理由不插入Echo方法。

有人知道为什么这个简单的方法不会被JIT内联。

3 个答案:

答案 0 :(得分:2)

经过进一步调查后,我得出以下结论:

  1. ILGenerator生成的方法永远不会内联。无论您是使用委托,其他DynamicMethod还是使用MethodBuilder创建的方法调用它们都无关紧要。
  2. 现有方法(用C#编码并由VS编译的方法)只有在使用MethodBuilder创建的方法调用时才能内联。如果从DynamicMethod调用它们,它们将永远不会内联。
  3. 在彻底测试了许多样本并查看最终汇编代码之后,我得出了这个结论。

答案 1 :(得分:0)

如果我理解正确,我的猜测是因为该方法是动态生成的,所以JIT编译器不知道为调用者内联它。

我已经编写了大量的IL,但我没有研究内联行为(主要是因为动态方法通常足够快,无需进一步优化)。

我欢迎有更多有关该主题的人提供反馈(请不要只是低估;如果我错了,我想在这里学到一些东西。)

<强>非动态

  • 您编写“普通”.NET代码(例如C#,VB.NET,任何支持CLS的语言)
  • IL在编译时创建
  • 机器码在运行时创建;方法在适当的地方内联

<强>动态

  • 您编写“普通”.NET代码,其目的是创建动态方法
  • 在编译时为此代码创建IL,但未创建动态方法
  • 在运行时为生成动态方法的代码创建机器代码
  • 调用该代码时,动态方法在特殊程序集中创建为IL
  • 动态方法的IL被编译为机器代码
  • 但是,JIT编译器不会重新编译其他调用者以内联新的动态方法。或者其他呼叫者本身也是动态的,尚未创建。

答案 2 :(得分:0)

您可以尝试生成动态程序集。我的理解是大多数运行时都不知道它是动态的。我认为内部它像任何其他byte []程序集一样被加载。因此,JIT /内联商可能也没有意识到它(或者不关心)。