Emisja IL: DynamicMethod

W przyszłym poście zajmę się słowem kluczowym dynamic od strony IL. Najpierw jednak chciałbym zaprezentować klasę DynamicMethod, ułatwiającą tworzenie własnych metod w oparciu o emisję IL. Jest ona bardzo prosta w użyciu i bez żadnej, skomplikowanej konfiguracji wystarczy po prostu wygenerować IL (co jest oczywiście trudnym zadaniem). Przykład:

MethodInfo writeLineMethod = typeof(Console).GetMethod("WriteLine", types: new Type[] { typeof(string) });

var dynamicMethod = new DynamicMethod("SayHello", typeof (string), new Type[] {}, typeof (Program));

ILGenerator generator = dynamicMethod.GetILGenerator();
generator.Emit(OpCodes.Ldstr, "Hello World !");
generator.EmitCall(OpCodes.Call, writeLineMethod, null);
generator.Emit(OpCodes.Ldstr, "Hello World 2 !");
generator.Emit(OpCodes.Ret);

var sayHelloMethod =(Func<string>)dynamicMethod.CreateDelegate(typeof(Func<string>));

string result = sayHelloMethod();

Wyemitowany IL wyświetli i zwróci tekst. Za pomocą Ldstr ładujemy na stos pierwszy napis, potem wykonujemy metodę Call, która pobierze ze stosu wspomniany napis. Na końcu zwracamy kolejny napis. Język IL opisywałem w kilkunastu postach więc jeśli instrukcje nie są jasne to zachęcam do poszperania na blogu.

Kolejne pytanie, po co nam to? Na pewno przyda się w analizowaniu internali i płynących z tego czasami optymalizacji. Jedną z tych optymalizacji jest zastąpienie mechanizmu refleksji dynamic method. Powyższy kod to nic innego jak wywołanie metody, do której nie mamy bezpośrednio dostępu.

Uproszczając przykład możemy:

MethodInfo toUpper = typeof (string).GetMethod("ToUpper", new Type[0]);            
var dynamicMethod = new DynamicMethod("ToUpperWrapper", typeof (string), new Type[] {typeof (string)});

ILGenerator il = dynamicMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, toUpper);
il.Emit(OpCodes.Ret);

var toUpperWrapper = (Func<string, string>) dynamicMethod.CreateDelegate(typeof (Func<string, string>));

string result = toUpperWrapper("Hello world");

Kod pokazuje jak wywołać metodę ToUpper tak jakbyśmy korzystali z reflection. Z tym, że jest to o WIELE szybsze i jeśli tylko musimy korzystać w aplikacji z reflection, lepiej to zrobić za pomocą DynamicMethod albo dynamic.

Napiszmy prosty benchmark:

class Program
{
   private const int N = 1000000000;
   private static void TestReflectionInvoke()
   {
       MethodInfo toUpper = typeof(string).GetMethod("ToUpper", new Type[0]);

       var stopwatch = Stopwatch.StartNew();
       object test = "Hello World";

       for (int i = 0; i < N; i++)
           test = toUpper.Invoke(test, null);

       stopwatch.Stop();
       Console.WriteLine("Reflection:{0}", stopwatch.ElapsedTicks);
   }
   private static void TestDynamicMethod()
   {                       
       MethodInfo toUpper = typeof (string).GetMethod("ToUpper", new Type[0]);
       
       var stopwatch = Stopwatch.StartNew();
       var dynamicMethod = new DynamicMethod("ToUpperWrapper", typeof (string), new Type[] {typeof (string)});

       ILGenerator il = dynamicMethod.GetILGenerator();
       il.Emit(OpCodes.Ldarg_0);
       il.Emit(OpCodes.Call, toUpper);
       il.Emit(OpCodes.Ret);
       
       var toUpperWrapper = (Func<string, string>) dynamicMethod.CreateDelegate(typeof (Func<string, string>));

       object test = "Hello World";

       for (int i = 0; i < N; i++)
           test = toUpperWrapper("Hello world");
   
       Console.WriteLine("Dynamic method:{0}", stopwatch.ElapsedTicks);
   }

   static void Main(string[] args)
   {
       TestDynamicMethod();
       TestReflectionInvoke();
   }

}

Różnica jest znacząca, na korzyść DynamicMethod:

image

One thought on “Emisja IL: DynamicMethod”

  1. Czesc, a gdyby TestreflectionInvoke() troche podrasowac:

    private static void TestReflectionInvoke()
    {
    MethodInfo toUpper = typeof(string).GetMethod(“ToUpper”, new Type[0]);

    Func action = (Func)Delegate.CreateDelegate(typeof(Func), toUpper);

    var stopwatch = Stopwatch.StartNew();
    string test = “Hello World”;

    for (int i = 0; i < N; i++)
    test = action(test); //test = toUpper.Invoke(test, null);

    stopwatch.Stop();
    Console.WriteLine("Reflection:{0}", stopwatch.ElapsedTicks);
    }

    Pozdrawiam

Leave a Reply

Your email address will not be published.