Linq : Différence de performance entre la syntaxe Linq et la syntaxe de Method

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

Published by Emmanuel Istace

Musician, Software developer and guitar instructor. https://emmanuelistace.be/

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: