Obecnie przeprowadzam optymalizacje ostatniej miary, głównie dla zabawy i nauki, i odkryłem coś, co pozostawiło mnie z kilkoma pytaniami.Ciekawość: Dlaczego funkcja Expression <...> podczas kompilacji działa szybciej niż minimalna metoda DynamicMethod?
pierwsze, pytania:
- Kiedy skonstruować metodę w pamięci dzięki zastosowaniu DynamicMethod i używać debuggera, czy jest jakiś sposób dla mnie krok do wygenerowanego kodu montażu, kiedy vieweing kod w widoku deasemblera? Debugger wydaje się po prostu przechodzić przez całą metodę dla mnie
- Albo, jeśli to nie jest możliwe, czy mogę w jakiś sposób zapisać wygenerowany kod IL na dysku jako zespół, tak, że mogę sprawdzić go za pomocą Reflector?
- Dlaczego moja prosta metoda dodawania (Int32 + Int32 => Int32) w wersji
Expression<...>
działa szybciej niż minimalna wersja DynamicMethod?
Oto krótki i kompletny program, który pokazuje. W moim systemie, wyjście jest:
DynamicMethod: 887 ms
Lambda: 1878 ms
Method: 1969 ms
Expression: 681 ms
Spodziewałem lambda i metoda nazywa mieć wyższe wartości, ale wersja DynamicMethod jest stale około 30-50% wolniej (prawdopodobnie ze względu na zmiany w systemie Windows i innych programów). Ktoś zna przyczynę?
Oto program:
using System;
using System.Linq.Expressions;
using System.Reflection.Emit;
using System.Diagnostics;
namespace Sandbox
{
public class Program
{
public static void Main(String[] args)
{
DynamicMethod method = new DynamicMethod("TestMethod",
typeof(Int32), new Type[] { typeof(Int32), typeof(Int32) });
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ret);
Func<Int32, Int32, Int32> f1 =
(Func<Int32, Int32, Int32>)method.CreateDelegate(
typeof(Func<Int32, Int32, Int32>));
Func<Int32, Int32, Int32> f2 = (Int32 a, Int32 b) => a + b;
Func<Int32, Int32, Int32> f3 = Sum;
Expression<Func<Int32, Int32, Int32>> f4x = (a, b) => a + b;
Func<Int32, Int32, Int32> f4 = f4x.Compile();
for (Int32 pass = 1; pass <= 2; pass++)
{
// Pass 1 just runs all the code without writing out anything
// to avoid JIT overhead influencing the results
Time(f1, "DynamicMethod", pass);
Time(f2, "Lambda", pass);
Time(f3, "Method", pass);
Time(f4, "Expression", pass);
}
}
private static void Time(Func<Int32, Int32, Int32> fn,
String name, Int32 pass)
{
Stopwatch sw = new Stopwatch();
sw.Start();
for (Int32 index = 0; index <= 100000000; index++)
{
Int32 result = fn(index, 1);
}
sw.Stop();
if (pass == 2)
Debug.WriteLine(name + ": " + sw.ElapsedMilliseconds + " ms");
}
private static Int32 Sum(Int32 a, Int32 b)
{
return a + b;
}
}
}
Interesujące pytanie. Tego typu rzeczy można rozwiązać za pomocą WinDebug i SOS. Przedstawiłem krok po kroku podobną analizę, którą zrobiłem wiele miesięcy temu na moim blogu, http://blog.barrkel.com/2006/05/clr-tailcall-optimization-lub-lack.html –
Myślałem, że powinienem pingować ty - dowiedziałem się, jak wymusić JIT bez konieczności wywoływania metody raz. Użyj argumentu konstruktora 'restrictedSkipVisibility' DynamicMethod. W zależności od kontekstu (zabezpieczenia kodu), może nie być dostępna. –
Naprawdę dobre pytanie. Po pierwsze, dla tego typu profilowania użyłbym release/Console - więc 'Debug.WriteLine' wygląda nie na miejscu; ale nawet z 'Console.WriteLine' moje statystyki są podobne: DynamicMethod: 630 ms Lambda: 561 ms Metoda: 553 ms Wyrażenie: 360 ms Ciągle szukam ... –