ILGenerator方法内联

use*_*808 7 .net methods cil inline 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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

Tim*_*ora 0

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

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

我欢迎对此主题更了解的人提供反馈(请不要只是否决;如果我错了,我想在这里学到一些东西)。

非动态

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

动态的

  • 您编写“正常”.NET 代码,其目的是创建动态方法
  • IL 是在编译时为此代码创建的,但未创建动态方法
  • 机器代码是在运行时为生成动态方法的代码创建的
  • 当调用该代码时,动态方法将在特殊程序集中创建为 IL
  • 动态方法的IL被编译为机器代码
  • 但是,JIT 编译器不会重新编译其他调用者来内联新的动态方法。或者其他调用者本身可能是动态的并且尚未创建。