Метод генерации ILGenerator - PullRequest
       36

Метод генерации ILGenerator

7 голосов
/ 31 декабря 2011

С учетом следующего кода:

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), а затем генерирую DynamicMethod с тем же циклом, что и данный основной метод, вызов метода Echo никогда не становится встроенным, и для завершения цикла требуется около 240 мс.Код MSIL правильный, так как он возвращает тот же результат, что и код C #.Я был уверен, что метод встраивания методов - это то, что делается JIT, поэтому я не вижу причин, по которым он не должен включать метод Echo.

Кто-нибудь знает, почему этот простой метод не будет встроен в JIT.

Ответы [ 3 ]

2 голосов
/ 31 декабря 2011

После дальнейшего изучения я пришел к выводу:

  1. Методы, сгенерированные ILGenerator, никогда не будут встроены.Неважно, вызываете ли вы их с помощью делегата, из другого DynamicMethod или из метода, созданного с помощью MethodBuilder.
  2. Существующие методы (те, которые написаны на C # и скомпилированы VS) могут быть встроены только при вызове изметод, созданный с помощью MethodBuilder.При вызове из DynamicMethod они никогда не будут встроены.

Я сделал это после тщательного тестирования многих примеров и изучения окончательного кода на ассемблере.

0 голосов
/ 01 января 2012

Вы можете попробовать создать динамическую сборку. Насколько я понимаю, большая часть времени выполнения не знает, что он динамический. Я думаю, что внутри он загружается как любая другая сборка byte []. Поэтому JIT / inliner также могут не знать об этом (или не будут заботиться).

0 голосов
/ 31 декабря 2011

Если я правильно понимаю, я предполагаю, что, поскольку метод генерируется динамически, JIT-компилятор не знает, как встроить его для вызывающих.

Я написал много IL, но я не изучал встроенное поведение (главным образом потому, что динамические методы обычно достаточно быстры для моих целей без дальнейшей оптимизации).

Я хотел бы, чтобы кто-то более осведомленный в этом вопросе дал отзыв (пожалуйста, не просто понизьте голос; я хотел бы узнать что-то здесь, если я ошибаюсь).

Нединамический

  • вы пишете «нормальный» код .NET (например, C #, VB.NET, любой язык с поддержкой CLS)
  • IL созданво время компиляции
  • машинный код создается во время выполнения;методы там, где это уместно,

Динамический

  • Вы пишете «обычный» код .NET, целью которого является создание динамического метода
  • IL создается во время компиляции для этого кода, но динамический метод не создается
  • машинный код создается во время выполнения для кода, который генерирует динамический метод
  • , когда этот код вызываетсядинамический метод создается как IL в специальной сборке
  • . IL динамического метода компилируется в машинный код
  • , однако JIT-компилятор не перекомпилирует другие вызывающие программы для включения нового динамического метода.Или, возможно, другие вызывающие абоненты сами по себе динамичны и еще не созданы.
...