Vitesse d'arbre d'expression Linq compilée

Ici, dans cet article dans les commentaires, il n'y avait pas qu'un différend, mais une certaine "non convergence" dans la comparaison de la vitesse d'IL Emit et de l'arbre d'expression Linq compilé.

Ce mini article est le code du test de vitesse + les résultats de l'exécution de ce test.

Pour le test, un code a été sélectionné qui intersecte le contenu de l'article d'origine - sérialisation en un flux de longueur d'une chaîne, puis de tous les octets d'une chaîne codée en UTF-16 (Encoding.Unicode).

Le code de sérialisation lui-même n'est peut-être pas le plus optimal, mais proche de cela si vous n'utilisez pas de constructions dangereuses.

Le code dans les deux implémentations est le même, comme vous pouvez le voir en examinant la construction de l'expression Lambda.

Je ne me suis pas soucié de la génération d'IL via Emit - le code qui devrait être "optimal" que je viens d'écrire en C # dans la méthode statique (en fait, toutes les méthodes du programme de test sont statiques, car il s'agit d'une application console très simple) - ce la méthode est en outre appelée Native .

La deuxième méthode de comparaison est l'expression générée par Lambda, compilée en appelant la méthode Compile (peut-être qu'un gain de vitesse peut être obtenu en utilisant CompileToMethod, mais ce n'est pas précis) - cette méthode est appelée aussi Expresisons .

BONUS! Pour une raison quelconque, un test supplémentaire a été ajouté - un appel indirect à la méthode utilisée dans le test natif - en affectant la méthode à un champ délégué statique - cette méthode est appelée Native Dlgt .

Au tout début de l'application, la sortie des deux méthodes est sortie, afin que vous puissiez vous assurer que le code génère exactement les mêmes données.

Voici donc le code de l'application (d'un seul coup):

Annonce
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; } } } 


Et voici les résultats des tests sur la configuration suivante:

CPU
	 Processeur Intel (R) Core (TM) i7-3770 à 3,40 GHz

	 Vitesse de base: 3,90 GHz
	 Prises: 1
	 Noyaux: 4
	 Processeurs logiques: 8
	 Virtualisation: activée
	 Cache L1: 256 Ko
	 Cache L2: 1,0 Mo
	 Cache L3: 8,0 Mo

	 Utilisation 8%
	 Vitesse 4,05 GHz
	 Temps de disponibilité 5: 00: 43: 01
	 Processus 239
	 Fils 4092
	 Poignées 168774


La mémoire
	 32,0 Go de DDR3

	 Vitesse: 1600 MHz
	 Emplacements utilisés: 4 sur 4
	 Facteur de forme: DIMM
	 Matériel réservé: 42,5 Mo

	 Disponible 20,7 Go
	 En cache 20,1 Go
	 Engagé 13,4 / 36,7 Go
	 Pool paginé 855 Mo
	 Pool non paginé 442 Mo
	 En cours d'utilisation (compressé) 11,2 Go (48,6 Mo)

Target .Net Framework 4.7
OS Windows 10 Pro x64 1803 build 17134.48

Donc, les résultats promis:

Compilation dans Debug, sans optimisation, lancement sans débogueur (Ctrl + F5):
TestHeure (durée)Heure (ticks)% temps
Natif00: 00: 01.576065115760651101,935%
DLG natif00: 00: 01.546147815461478100%
Expressions00: 00: 01.583545415835454102,4188%

Compilation en Release, avec optimisation, démarrage sans débogueur (Ctrl + F5):
TestHeure (durée)Heure (ticks)% temps
Natif00: 00: 01.3182291
13182291
100%
DLG natif00: 00: 01.3300925
13300925
100,8999%
Expressions00: 00: 01.4871786
14871786
112,8164%

Vous pouvez résumer certains des résultats:

  • L'arbre d'expression compilé s'exécute 1 à 2% plus lentement dans le débogage et 10 à 12 dans la version, ce qui est très bien, étant donné que la génération de code via l'arbre d'expression en cours d'exécution est beaucoup plus facile.
  • En mode débogage, pour une raison quelconque, un appel de méthode indirecte via un délégué est plus rapide qu'un appel direct.

Bonus numéro 2 sous le spoiler - Représentation du débogage des expressions Lambda avant la compilation:

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);
         .Appelez $ stream.Write (
             $ intBuff,
             0
             $ intBuff.Length);
         .Appelez $ stream.Write (
             $ strBuff,
             0
             $ strBuff.Length)
     }
 }

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


All Articles