Salut a tous, aujourd’hui un article sur la différence de performance entre deux syntaxe pour écrire des requêtes Linq avec a la fin un spécial winner ^^
Alors, tout d’abord, voici ce qu’est une syntaxe linq :
var x = from t in test where t != 0 select t;
Et voici une syntaxe de méthode :
var x = test.Where(i => i != 0) .Select(i => i);
Pour ma part, du fait que la syntaxe Linq faisait appel a une interprétation a la compilation j’ai toujours pensé qu’elle était plus lente que la syntaxe en méthode. Mais nous allons voir que finalement, pas du tout.
Voici les deux méthodes que nous allons comparer :
static void MethodSyntax() { List test = new List(); test.Add(0); var x = test.Where(i => i != 0).Select(i => i); } static void LinqSyntax() { List test = new List(); test.Add(0); var x = from t in test where t != 0 select t; }
Et voici le Code IL généré :
Première Method :
.method private hidebysig static void MethodSyntax() cil managed { // Code size 90 (0x5a) .maxstack 4 .locals init ([0] class [mscorlib]System.Collections.Generic.List`1 test, [1] class [mscorlib]System.Collections.Generic.IEnumerable`1 x) IL_0000: nop IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1::.ctor() IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: ldc.i4.0 IL_0009: callvirt instance void class [mscorlib]System.Collections.Generic.List`1::Add(!0) IL_000e: nop IL_000f: ldloc.0 IL_0010: ldsfld class [mscorlib]System.Func`2 Linq.Program::'CS$<>9__CachedAnonymousMethodDelegate2' IL_0015: brtrue.s IL_002a IL_0017: ldnull IL_0018: ldftn bool Linq.Program::'b__0'(int32) IL_001e: newobj instance void class [mscorlib]System.Func`2::.ctor(object, native int) IL_0023: stsfld class [mscorlib]System.Func`2 Linq.Program::'CS$<>9__CachedAnonymousMethodDelegate2' IL_0028: br.s IL_002a IL_002a: ldsfld class [mscorlib]System.Func`2 Linq.Program::'CS$<>9__CachedAnonymousMethodDelegate2' IL_002f: call class [mscorlib]System.Collections.Generic.IEnumerable`1 [System.Core]System.Linq.Enumerable::Where(class [mscorlib]System.Collections.Generic.IEnumerable`1, class [mscorlib]System.Func`2) IL_0034: ldsfld class [mscorlib]System.Func`2 Linq.Program::'CS$<>9__CachedAnonymousMethodDelegate3' IL_0039: brtrue.s IL_004e IL_003b: ldnull IL_003c: ldftn int32 Linq.Program::'b__1'(int32) IL_0042: newobj instance void class [mscorlib]System.Func`2::.ctor(object, native int) IL_0047: stsfld class [mscorlib]System.Func`2 Linq.Program::'CS$<>9__CachedAnonymousMethodDelegate3' IL_004c: br.s IL_004e IL_004e: ldsfld class [mscorlib]System.Func`2 Linq.Program::'CS$<>9__CachedAnonymousMethodDelegate3' IL_0053: call class [mscorlib]System.Collections.Generic.IEnumerable`1 [System.Core]System.Linq.Enumerable::Select(class [mscorlib]System.Collections.Generic.IEnumerable`1, class [mscorlib]System.Func`2) IL_0058: stloc.1 IL_0059: ret } // end of method Program::MethodSyntax
Avec Linq :
.method private hidebysig static void LinqSyntax() cil managed { // Code size 54 (0x36) .maxstack 4 .locals init ([0] class [mscorlib]System.Collections.Generic.List`1 test, [1] class [mscorlib]System.Collections.Generic.IEnumerable`1 x) IL_0000: nop IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1::.ctor() IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: ldc.i4.0 IL_0009: callvirt instance void class [mscorlib]System.Collections.Generic.List`1::Add(!0) IL_000e: nop IL_000f: ldloc.0 IL_0010: ldsfld class [mscorlib]System.Func`2 Linq.Program::'CS$<>9__CachedAnonymousMethodDelegate5' IL_0015: brtrue.s IL_002a IL_0017: ldnull IL_0018: ldftn bool Linq.Program::'b__4'(int32) IL_001e: newobj instance void class [mscorlib]System.Func`2::.ctor(object, native int) IL_0023: stsfld class [mscorlib]System.Func`2 Linq.Program::'CS$<>9__CachedAnonymousMethodDelegate5' IL_0028: br.s IL_002a IL_002a: ldsfld class [mscorlib]System.Func`2 Linq.Program::'CS$<>9__CachedAnonymousMethodDelegate5' IL_002f: call class [mscorlib]System.Collections.Generic.IEnumerable`1 [System.Core]System.Linq.Enumerable::Where(class [mscorlib]System.Collections.Generic.IEnumerable`1, class [mscorlib]System.Func`2) IL_0034: stloc.1 IL_0035: ret } // end of method Program::LinqSyntax
A ma plus grand surprise on peut voir que le code IL est plus lourd sur les méthode que sur la syntaxe. En effet, si l’on observe le code on voit que Linq appel un delegate alors que Method en appel deux. Si l’on s’y penche d’un peu plus près on voit que le delegate appelé par Linq et le premier appelé par Methode est le même bien qu’étant stocké deux fois sous des noms différent.
Que font ces delegate :
.method private hidebysig static bool 'b__4'(int32 t) cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) .maxstack 2 .locals init ([0] bool CS$1$0000) IL_0000: ldarg.0 // Load l'arguments 0 IL_0001: ldc.i4.0 // Met 0 sur la stack IL_0002: ceq // Equivalent a un if equal IL_0004: ldc.i4.0 // Met 0 sur la stack IL_0005: ceq // Equivalent a un if equal IL_0007: stloc.0 // Pop le premier élément stack de la stack et la store dans local 0 IL_0008: br.s IL_000a // se branche sur l'instruction 000a IL_000a: ldloc.0 // Charge sur l'evaluation stack la variable a l'index 0 IL_000b: ret // retour } .method private hidebysig static int32 'b__1'(int32 i) cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Code size 6 (0x6) .maxstack 1 .locals init ([0] int32 CS$1$0000) IL_0000: ldarg.0 // Charge l'argument 0 IL_0001: stloc.0 // Pop le premier élément stack de la stack et la store dans local 0 IL_0002: br.s IL_0004 // se branche sur l'instruction 0004 IL_0004: ldloc.0 // Charge sur l'evaluation stack la variable a l'index 0 IL_0005: ret // Charge }
On comprend donc facilement qu’il s’agit pour le premier du where.
Et en fait, dans le code MSIL des deux méthodes, tout ce qui est dans la version Linq se trouve dans la version Method. Method exécute juste plus de code, c’est-à-dire de IL_0034 a IL 0053 dans lequel le delegate spécifique a Method est appelé.
Regardons au final les perfs. Sur un tableau de 1 000 000 000 (un milliard) d’integer ou on veut selectionner tous les int différents de 0. L’indice est exprimé en clock thick et se base sur 100 expériences par méthode dans lesquels on a exclus le premier et dernier décile (c’est a dire les extrêmes, considérée comme anomalies). Si vous voulez les nombres obtenus complets les voici : http://pastebin.com/TVy7F4Xq
(Effectué sur un portable monocore 2.8Ghz avec 4Go de ram)
Temp moyen avec Linq en thicks | Temp moyen avec Method en thicks |
9 214 | 21 506 |
On voit donc que, en moyenne, le code généré par une requête en syntaxe Linq sera 2,33 fois plus rapide que le code généré par une syntaxe Method.
Vous pouvez donc meller code lisible et perfs sans trop de soucis a présent ^^
Inform@tiquement
Manu404