8

给定以下代码:

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 内联。

4

3 回答 3

3

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

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

在彻底测试了许多样本并查看了最终的汇编代码后,我得出了这一结论。

于 2011-12-31T16:15:08.263 回答
0

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

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

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

非动态

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

动态的

  • 您编写“普通”.NET 代码,其目的是创建动态方法
  • 此代码在编译时创建 IL,但未创建动态方法
  • 在运行时为生成动态方法的代码创建机器代码
  • 当调用该代码时,动态方法在特殊程序集中创建为 IL
  • 动态方法的 IL 被编译为机器码
  • 但是,JIT 编译器不会重新编译其他调用者来内联新的动态方法。或者其他调用者本身是动态的并且尚未创建。
于 2011-12-31T00:17:14.970 回答
0

您可以尝试生成动态程序集。我的理解是大多数运行时都不知道它是动态的。我认为在内部它会像任何其他 byte[] 程序集一样被加载。因此 JIT/inliner 也可能不知道它(或不在乎)。

于 2011-12-31T20:48:41.737 回答