web-dev-qa-db-fra.com

Performances "imbriquée pour chaque" vs "requête lambda / linq" (LINQ-to-Objects)

Du point de vue des performances, que devez-vous utiliser "Nested foreach's" ou "lambda/linq queries"?

39
JSC

Écrivez le code le plus clair possible, puis comparez et profilez pour découvrir les problèmes de performances. Si vous avez des problèmes de performances, vous pouvez expérimenter avec du code différent pour déterminer s'il est plus rapide ou non (mesurer tout le temps avec des données aussi réalistes que possible), puis faire un jugement pour savoir si le l'amélioration des performances vaut le coup de la lisibilité.

Une approche directe foreachsera plus rapide que LINQ dans de nombreux cas. Par exemple, considérez:

var query = from element in list
            where element.X > 2
            where element.Y < 2
            select element.X + element.Y;

foreach (var value in query)
{
    Console.WriteLine(value);
}

Il y a maintenant deux clauses where et une clause select, donc chaque élément éventuel doit passer par trois itérateurs. (Évidemment, les deux clauses where pourraient être combinées dans ce cas, mais je fais une remarque générale.)

Maintenant, comparez-le avec le code direct:

foreach (var element in list)
{
    if (element.X > 2 && element.Y < 2)
    {
        Console.WriteLine(element.X + element.Y);
    }
}

Cela fonctionnera plus rapidement, car il a moins de cerceaux à parcourir. Il est probable que la sortie de la console éclipsera le coût de l'itérateur, et je préférerais certainement la requête LINQ.

EDIT: Pour répondre à propos des boucles "nested foreach" ... celles-ci sont généralement représentées par SelectMany ou une deuxième clause from:

var query = from item in firstSequence
            from nestedItem in item.NestedItems
            select item.BaseCount + nestedItem.NestedCount;

Ici, nous ajoutons uniquement un seul itérateur supplémentaire, car nous utiliserions déjà un itérateur supplémentaire par élément dans la première séquence en raison de la boucle imbriquée foreach. Il y a encore un peu de surcharge, y compris la surcharge de faire la projection dans un délégué au lieu de "inline" (quelque chose que je n'ai pas mentionné auparavant) mais ce ne sera toujours pas très différent de la performance imbriquée.

Cela ne veut pas dire que vous ne pouvez pas vous tirer une balle dans le pied avec LINQ, bien sûr. Vous pouvez écrire des requêtes incroyablement inefficaces si vous n'engagez pas votre cerveau en premier - mais c'est loin d'être unique à LINQ ...

60
Jon Skeet

Si tu fais

foreach(Customer c in Customer)
{
  foreach(Order o in Orders)
  {
    //do something with c and o
  }
}

Vous effectuerez les itérations Customer.Count * Order.Count


Si tu fais

var query =
  from c in Customer
  join o in Orders on c.CustomerID equals o.CustomerID
  select new {c, o}

foreach(var x in query)
{
  //do something with x.c and x.o
}

Vous effectuerez des itérations Customer.Count + Order.Count, car Enumerable.Join est implémenté en tant que HashJoin.

23
Amy B

C'est plus complexe là-dessus. En fin de compte, une grande partie de LINQ-to-Objects est (en coulisses) une boucle foreach, mais avec la surcharge supplémentaire d'un petit bloc d'abstraction/itérateur/etc. Cependant, à moins que vous ne fassiez des choses très différentes dans vos deux (foreach vs LINQ), elles doivent toutes deux être O (N).

La vraie question est: existe-t-il une meilleure façon d'écrire votre algorithme spécifique qui signifie que foreach serait inefficace? Et LINQ peut-il le faire pour vous?

Par exemple, LINQ facilite le hachage/regroupement/tri des données.

12
Marc Gravell

Cela a déjà été dit, mais cela mérite d'être répété.

Les développeurs ne savent jamais où se situe le goulot d'étranglement des performances tant qu'ils n'exécutent pas de tests de performances.

La même chose est vraie pour comparer la technique A à la technique B. Sauf s'il y a une différence dramatique, il suffit de la tester. Cela peut être évident si vous avez un scénario O(n) vs O (n ^ x), mais comme le contenu LINQ est principalement de la sorcellerie de compilateur, il mérite un profilage.

En outre, sauf si votre projet est en production et que vous avez profilé le code et constaté que cette boucle ralentit votre exécution, laissez-la selon votre préférence pour la lisibilité et la maintenance. L'optimisation prématurée est le diable.

2
Drithyin

Un grand avantage est que l'utilisation de requêtes Linq-To-Objects vous donne la possibilité de transférer facilement la requête vers PLinq et de demander au système d'effectuer automatiquement l'opération sur le nombre correct de threads pour le système actuel.

Si vous utilisez cette technique sur des ensembles de données volumineux, cela devient facilement une grosse victoire pour très peu de problèmes.

2
Denis Troller