Geschwindigkeit des kompilierten Linq Expression Tree

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):

Auflistung
using 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):
TestZeit (Zeitspanne)Zeit (Ticks)% Zeit
Einheimisch00: 00: 01.576065115760651101,935%
Native dlgt00: 00: 01.546147815461478100%
Ausdrücke00: 00: 01.583545415835454102,4188%

Kompilierung in Release mit Optimierung, Start ohne Debugger (Strg + F5):
TestZeit (Zeitspanne)Zeit (Ticks)% Zeit
Einheimisch00: 00: 01.3182291
13182291
100%
Native dlgt00: 00: 01.3300925
13300925
100,8999%
Ausdrücke00: 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)
     }}
 }}

Source: https://habr.com/ru/post/de413561/


All Articles