Ich mache derzeit einige Optimierungsmaßnahmen, hauptsächlich zum Spaß und zum Lernen, und habe etwas entdeckt, das mich mit ein paar Fragen zurückgelassen hat.Kuriosität: Warum wird Expression <...> bei der Kompilierung schneller als eine minimale DynamicMethod ausgeführt?
Zunächst werden die Fragen:
- Wenn ich eine Methode in-Speicher durch die Verwendung von DynamicMethod konstruieren, und der Debugger verwenden, gibt es eine Möglichkeit für mich in den erzeugten Assembler-Code zu Schritt, wenn vieweing der Code in der Disassembler-Ansicht? Der Debugger scheint nur über die ganze Methode für mich zu gehen
- Oder, wenn das nicht möglich ist, ist es mir möglich, den generierten IL-Code auf der Festplatte als eine Baugruppe zu speichern, so dass ich es mit Reflector überprüfen kann?
- Warum läuft die
Expression<...>
-Version meiner einfachen Additionsmethode (Int32 + Int32 => Int32) schneller als eine minimale DynamicMethod-Version?
Hier ist ein kurzes und komplettes Programm, das demonstriert. Auf meinem System ist die Ausgabe:
DynamicMethod: 887 ms
Lambda: 1878 ms
Method: 1969 ms
Expression: 681 ms
ich das Lambda und Verfahren zu erwarten fordern höhere Werte zu haben, aber die Dynamic Version ist durchweg etwa 30-50% langsamer (Variationen wahrscheinlich aufgrund von Windows und anderen Programmen). Wer kennt den Grund?
Hier ist das Programm:
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;
}
}
}
Interessante Frage. Diese Dinge können mit WinDebug und SOS gelöst werden. Ich veröffentlichte eine Schritt für Schritt eine ähnliche Analyse, die ich vor vielen Monden in meinem Blog gemacht habe, http://blog.barrkel.com/2006/05/clr-tailcall-optimization-or-lack.html –
Ich dachte, ich sollte pingen Du - Ich habe herausgefunden, wie man JIT erzwingt, ohne die Methode einmal aufrufen zu müssen. Verwenden Sie das Konstruktorargument 'restrictedSkipVisibility' DynamicMethod. Je nach Kontext (Codesicherheit) ist es jedoch möglicherweise nicht verfügbar. –
Wirklich gute Frage. Zuerst würde ich für diese Art von Profiling ein Release/Console verwenden - also sieht die 'Debug.WriteLine' fehl; aber selbst mit 'Console.WriteLine' sind meine Werte ähnlich: DynamicMethod: 630 ms Lambda: 561 ms Methode: 553 ms Ausdruck: 360 ms Ich suche noch ... –