Hier in
diesem Artikel, in den Kommentaren, gab es nichts zu streiten, aber eine gewisse „Nichtkonvergenz“ beim Vergleich der Geschwindigkeit von IL Emit und des kompilierten Linq Expression Tree.
Dieser Mini-Artikel ist der Code des Geschwindigkeitstests + die Ergebnisse des Testlaufs.
Für den Test wurde ein Code ausgewählt, der sich mit dem Inhalt des Originalartikels überschneidet - Serialisierung in einen Stream der Länge einer Zeichenfolge und dann aller Bytes einer in UTF-16 (Encoding.Unicode) codierten Zeichenfolge.
Der Serialisierungscode selbst ist möglicherweise nicht der optimalste, liegt jedoch nahe daran, wenn Sie keine unsicheren Konstrukte verwenden.
Der Code in beiden Implementierungen ist der gleiche, wie Sie anhand der Konstruktion des Lambda-Ausdrucks sehen können.
Ich habe mich nicht um die IL-Generierung durch Emit gekümmert - den Code, der "optimal" sein sollte, den ich gerade in C # in der statischen Methode geschrieben habe (tatsächlich sind alle Methoden des Testprogramms statisch, da dies eine sehr einfache Konsolenanwendung ist) - dies Die Methode wird weiterhin
Native genannt .
Die zweite Vergleichsmethode ist der von Lambda generierte Ausdruck, der durch Aufrufen der Compile-Methode kompiliert wurde (möglicherweise kann mit CompileToMethod eine Geschwindigkeitssteigerung erzielt werden, dies ist jedoch nicht genau). Diese Methode wird auch als
Expresisons bezeichnet .
BONUS! Aus irgendeinem Grund wurde ein zusätzlicher Test hinzugefügt - ein indirekter Aufruf der im Native- Test verwendeten Methode -, indem die Methode einem statischen Delegatenfeld zugewiesen wurde . Diese Methode heißt Native Dlgt .Zu Beginn der Anwendung wird die Ausgabe beider Methoden ausgegeben, sodass Sie sicherstellen können, dass der Code genau dieselben Daten generiert.
Hier ist also der Anwendungscode (auf einmal):
Auflistungusing System; using System.Diagnostics; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text; namespace ExpressionSpeedTest { class Program { static void Main(string[] args) { InitExpression(); InitDelegateNative(); var inst = new TestClass { StringProp = "abcdefabcdef" }; byte[] buff1, buff2; using (var ms1 = new MemoryStream()) { SaveString(ms1, inst); buff1 = ms1.ToArray(); } using (var ms2 = new MemoryStream()) { DynamicMethod(ms2, inst); buff2 = ms2.ToArray(); } Console.WriteLine($"Native string: {string.Join("", buff1.Select(b => Encoding.Default.GetString(new[] { b })))}"); Console.WriteLine($"Expressions string: {string.Join("", buff2.Select(b => Encoding.Default.GetString(new[] { b })))}"); GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); GC.Collect(GC.MaxGeneration); TestNative(); GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); GC.Collect(GC.MaxGeneration); TestDelegateNative(); GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); GC.Collect(GC.MaxGeneration); TestExpressions(); GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); GC.Collect(GC.MaxGeneration); Console.ReadLine(); } private static void TestDelegateNative() { var inst = new TestClass { StringProp = "abcdefabcdef" }; using (var ms = new MemoryStream()) { var sw = new Stopwatch(); sw.Start(); for (var idx = 0; idx < loopLength; idx++) { SaveString(ms, inst); } sw.Stop(); Console.WriteLine($"Native Dlgt test: {sw.Elapsed}, {sw.ElapsedTicks} ticks"); } } private static void InitDelegateNative() { NativeDelegate = SaveString; } private static void InitExpression() { var intGetBytes = typeof(BitConverter).GetMethods(BindingFlags.Static | BindingFlags.Public) .Single(x => x.Name == nameof(BitConverter.GetBytes) && x.GetParameters()[0].ParameterType == typeof(int)); var stringGetBytes = typeof(Encoding).GetMethods(BindingFlags.Instance | BindingFlags.Public) .Single(x => x.Name == nameof(Encoding.GetBytes) && x.GetParameters().Length == 1 && x.GetParameters()[0].ParameterType == typeof(string)); var unicodeProp = typeof(Encoding).GetProperty(nameof(Encoding.Unicode), BindingFlags.Static | BindingFlags.Public); var streamWrite = typeof(Stream).GetMethod(nameof(Stream.Write)); var streamPar = Expression.Parameter(typeof(Stream), "stream"); var instPar = Expression.Parameter(typeof(TestClass), "inst"); var intBuffVar = Expression.Variable(typeof(byte[]), "intBuff"); var strBuffVar = Expression.Variable(typeof(byte[]), "strBuff"); var expressionBody = Expression.Block( new[] { intBuffVar, strBuffVar }, Expression.Assign(intBuffVar, Expression.Call(null, intGetBytes, Expression.Property( Expression.Property( instPar, nameof(TestClass.StringProp)), nameof(string.Length)))), Expression.Assign(strBuffVar, Expression.Call(Expression.Property(null, unicodeProp), stringGetBytes, Expression.Property( instPar, nameof(TestClass.StringProp) ))), Expression.Call(streamPar, streamWrite, intBuffVar, Expression.Constant(0), Expression.Property(intBuffVar, nameof(Array.Length))), Expression.Call(streamPar, streamWrite, strBuffVar, Expression.Constant(0), Expression.Property(strBuffVar, nameof(Array.Length))) ); DynamicMethod = Expression.Lambda<Action<Stream, TestClass>>(expressionBody, streamPar, instPar).Compile(); } private const int loopLength = 10000000; private static Action<Stream, TestClass> DynamicMethod; private static Action<Stream, TestClass> NativeDelegate; private static void TestExpressions() { var inst = new TestClass { StringProp = "abcdefabcdef" }; using (var ms = new MemoryStream()) { var sw = new Stopwatch(); sw.Start(); for (var idx = 0; idx < loopLength; idx++) { DynamicMethod(ms, inst); } sw.Stop(); Console.WriteLine($"Expressions test: {sw.Elapsed}, {sw.ElapsedTicks} ticks"); } } private static void TestNative() { var inst = new TestClass { StringProp = "abcdefabcdef" }; using (var ms = new MemoryStream()) { var sw = new Stopwatch(); sw.Start(); for (var idx = 0; idx < loopLength; idx++) { SaveString(ms, inst); } sw.Stop(); Console.WriteLine($"Native test: {sw.Elapsed}, {sw.ElapsedTicks} ticks"); } } public static void SaveString(Stream stream, TestClass instance) { var intBuff = BitConverter.GetBytes(instance.StringProp.Length); var strBuff = Encoding.Unicode.GetBytes(instance.StringProp); stream.Write(intBuff, 0, intBuff.Length); stream.Write(strBuff, 0, strBuff.Length); } } class TestClass { public string StringProp { get; set; } } }
Und hier sind die Testergebnisse für die folgende Konfiguration:
CPU Intel (R) Core (TM) i7-3770-CPU bei 3,40 GHz
Grundgeschwindigkeit: 3,90 GHz
Steckdosen: 1
Kerne: 4
Logische Prozessoren: 8
Virtualisierung: Aktiviert
L1-Cache: 256 KB
L2-Cache: 1,0 MB
L3-Cache: 8,0 MB
Auslastung 8%
Geschwindigkeit 4,05 GHz
Betriebszeit 5: 00: 43: 01
Prozesse 239
Threads 4092
Griffe 168774
Speicher 32,0 GB DDR3
Geschwindigkeit: 1600 MHz
Verwendete Steckplätze: 4 von 4
Formfaktor: DIMM
Hardware reserviert: 42,5 MB
Verfügbar 20,7 GB
Zwischengespeicherte 20,1 GB
Festgeschriebene 13,4 / 36,7 GB
Seitenpool 855 MB
Nicht ausgelagerter Pool 442 MB
In Verwendung (komprimiert) 11,2 GB (48,6 MB)
Ziel .Net Framework 4.7
Betriebssystem Windows 10 Pro x64 1803 Build 17134.48
Also, die versprochenen Ergebnisse:
Kompilierung in Debug, ohne Optimierung, Start ohne Debugger (Strg + F5):
Test | Zeit (Zeitspanne) | Zeit (Ticks) | % Zeit |
---|
Einheimisch | 00: 00: 01.5760651 | 15760651 | 101,935% |
Native dlgt | 00: 00: 01.5461478 | 15461478 | 100% |
Ausdrücke | 00: 00: 01.5835454 | 15835454 | 102,4188% |
Kompilierung in Release mit Optimierung, Start ohne Debugger (Strg + F5):
Test | Zeit (Zeitspanne) | Zeit (Ticks) | % Zeit |
---|
Einheimisch | 00: 00: 01.3182291
| 13182291
| 100% |
Native dlgt | 00: 00: 01.3300925
| 13300925
| 100,8999% |
Ausdrücke | 00: 00: 01.4871786
| 14871786
| 112,8164% |
Sie können einige der Ergebnisse zusammenfassen:
- Der kompilierte Ausdrucksbaum läuft im Debug 1-2% langsamer und im Release 10-12 langsamer, was sehr gut ist, wenn man bedenkt, dass das Generieren von Code über den Ausdrucksbaum zur Laufzeit viel einfacher ist.
- Im Debug-Modus ist aus irgendeinem Grund ein indirekter Methodenaufruf über einen Delegaten schneller als ein direkter Aufruf.
Bonus Nummer 2 unter dem Spoiler - Debug-Darstellung von Lambda-Ausdrücken vor der Kompilierung:
Lambda .Lambda # Lambda1 <System.Action`2 [System.IO.Stream, ExpressionSpeedTest.TestClass]> (
System.IO.Stream $ stream,
ExpressionSpeedTest.TestClass $ inst) {
.Block (
System.Byte [] $ intBuff,
System.Byte [] $ strBuff) {
$ intBuff = .Call System.BitConverter.GetBytes (($ inst.StringProp) .Length);
$ strBuff = .Call (System.Text.Encoding.Unicode) .GetBytes ($ inst.StringProp);
Rufen Sie $ stream.Write (
$ intBuff,
0
$ intBuff.Length);
Rufen Sie $ stream.Write (
$ strBuff,
0
$ strBuff.Length)
}}
}}