.net - ILGenerator 方法内联

标签 .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 毫秒内完成。到目前为止,一切都很好。当我使用 ILGenerator 生成 Do 方法 (Echo),然后使用与给定 main 方法相同的循环生成 DynamicMethod 时,对 Echo 方法的调用永远不会内联,并且循环完成大约需要 240 毫秒。 MSIL 代码是正确的,因为它返回与 C# 代码相同的结果。我确信方法内联是由 JIT 完成的,所以我认为没有理由不内联 Echo 方法。

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

最佳答案

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

  1. ILGenerator 生成的方法永远不会被内联。无论您是使用委托(delegate)、从另一个 DynamicMethod 还是从使用 MethodBuilder 创建的方法调用它们,都没有关系。
  2. 现有方法(用 C# 编码并由 VS 编译的方法)仅当从使用 MethodBuilder 创建的方法调用时才能内联。如果从 DynamicMethod 调用,它们将永远不会被内联。

我在彻底测试了许多示例并研究了最终的汇编代码后得出了这一结论。

关于.net - ILGenerator 方法内联,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8685263/

相关文章:

javascript - 在 filter() 方法中返回值而不是对象

haskell - 内部模块的跨模块内联

java - 从 try 中获取一个变量

java - 从 main 方法传递一个字符串以在同一类文件中进行绘制

java - 保证 Java 客户端可以使用 .NET WCF 服务

.net - dot net maui 图像控件不显示远程源

c++ - 将静态全局变量声明为内联是否有意义?

javascript - 如何将文本文件包含到javascript中

c# - 如何使用 C#/.NET ODBC 或 OLE 读/写 dBase III 文件?

c# - C# 中的编译器是否会删除始终为假的条件