ILGenerator 方法内联

发布于 2024-12-23 20:19:02 字数 2892 浏览 1 评论 0原文

给出以下代码:

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

孤君无依 2024-12-30 20:19:02

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

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

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

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.

清风无影 2024-12-30 20:19:02

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

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

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

非动态

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

动态

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

If I'm understanding correctly, my guess is that since the method is generated dynamically, the JIT compiler doesn't know to inline it for callers.

I have written a great deal of IL but I haven't looked into inlining behavior (mainly because dynamic methods are usually fast enough for my purposes without further optimization).

I would welcome someone more knowledgeable on the subject to give feedback (please don't just downvote; I would like to learn something here if I am wrong).

Non-Dynamic

  • you write "normal" .NET code (e.g. C#, VB.NET, any CLS-aware language)
  • IL is created at compile time
  • machine code is created at runtime; methods are inlined where appropriate

Dynamic

  • you write "normal" .NET code whose purpose is to create a dynamic method
  • IL is created at compile time for this code, but the dynamic method is not created
  • machine code is created at runtime for the code which generates the dynamic method
  • when that code is invoked, the dynamic method is created as IL in a special assembly
  • dynamic method's IL is compiled to machine code
  • however, JIT compiler doesn't recompile other callers to inline the new dynamic method. Or perhaps the other callers are dynamic themselves and haven't yet been created.
时间你老了 2024-12-30 20:19:02

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

You might try generating a dynamic assembly. My understanding is that most of the runtime is not aware that it is dynamic. I think internally it gets loaded like any other byte[] assembly. Therefore the JIT/inliner might also not be aware of it (or won't care).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文