LINQ doit-il être évité parce que c’est lent?

On m’avait dit que puisque .net linq est si lent que nous ne devrions pas l’utiliser et se demandait si quelqu’un d’autre était arrivé à la même conclusion, par exemple:

Pris 1443ms à faire 1000000000 compare non-LINQ.
A pris 4944ms pour faire 1000000000 se compare à LINQ.
(243% moins rapide)

le code non-LINQ:

for (int i = 0; i < 10000; i++) { foreach (MyLinqTestClass1 item in lst1) //100000 items in the list { if (item.Name == "9999") { isInGroup = true; break; } } } 

Pris 1443ms à faire 1000000000 compare non-LINQ.

Code LINQ:

 for (int i = 0; i < 10000; i++) isInGroup = lst1.Cast().Any(item => item.Name == "9999"); 

A pris 4944ms pour faire 1000000000 se compare à LINQ.

J’imagine qu’il est possible d’optimiser le code LINQ, mais l’idée était de trouver facilement du code LINQ très lent et de ne pas l’utiliser. Étant donné que LINQ est lent, il en résulterait également que PLINQ est lent et que NHibernate LINQ serait lent, de sorte que tout type sur l’instruction LINQ ne devrait pas être utilisé.

Quelqu’un d’autre a-t-il trouvé que LINQ est lent au fait qu’il ne l’a jamais utilisé ou est-ce que nous commetsortingons une commotion cérébrale trop générale basée sur des points de référence comme celui-ci?

Linq doit-il être évité parce que c’est lent?

Non, cela devrait être évité s’il n’est pas assez rapide . Lent et pas assez rapide n’est pas du tout la même chose!

Lent n’est pas pertinent pour vos clients, vos dirigeants et vos parties prenantes. Pas assez rapide est extrêmement pertinent. Ne jamais mesurer à quelle vitesse quelque chose est; cela ne vous dit rien que vous pouvez utiliser pour fonder une décision commerciale. Mesurez à quel point le client est acceptable . Si c’est acceptable, arrêtez de dépenser de l’argent pour le rendre plus rapide; c’est déjà assez bon.

L’optimisation des performances est coûteuse . Ecrire du code pour qu’il puisse être lu et maintenu par d’autres est coûteux . Ces objectives sont souvent en opposition les uns avec les autres, donc pour dépenser l’argent de vos parties prenantes de manière responsable, vous devez vous assurer que vous ne consacrez que du temps et des efforts précieux à optimiser les performances sur des choses pas assez rapides .

Vous avez trouvé une situation de référence artificielle et irréaliste où le code LINQ est plus lent que toute autre façon d’écrire le code. Je vous assure que vos clients ne se soucient pas un peu de la vitesse de votre benchmark irréaliste. Ils se soucient seulement si le programme que vous leur expédiez est trop lent pour eux. Et je vous assure que votre gestion ne s’en soucie pas un peu (s’ils sont compétents); ils se soucient de la quantité d’argent que vous dépensez inutilement pour fabriquer des choses assez rapidement assez vite, et rendre le code plus cher à lire, à comprendre et à maintenir dans le processus.

Pourquoi utilisez-vous Cast() ? Vous ne nous avez pas donné suffisamment de code pour vraiment juger le benchmark, en gros.

Oui, vous pouvez utiliser LINQ pour écrire du code lent. Devine quoi? Vous pouvez aussi écrire du code lent non-LINQ.

LINQ facilite grandement l’ expressivité du code traitant des données … et ce n’est pas si difficile d’écrire du code qui fonctionne bien, du moment que vous prenez le temps de comprendre LINQ pour commencer.

Si quelqu’un me disait de ne pas utiliser LINQ (surtout LINQ to Objects) pour des raisons de rapidité perçues , je ris dans leur visage. S’ils ont trouvé un goulot d’étranglement spécifique et ont dit: “Nous pouvons accélérer la cadence en n’utilisant pas LINQ dans cette situation, et voici la preuve”, alors c’est très différent.

J’ai peut-être raté quelque chose, mais je suis certain que vos points de repère sont éteints.

J’ai testé avec les méthodes suivantes:

  • La méthode d’extension Any (“LINQ”)
  • Une simple boucle foreach (votre méthode “optimisée”)
  • Utiliser la méthode ICollection.Contains
  • La méthode d’extension Any utilisant une structure de données optimisée ( HashSet )

Voici le code de test:

 class Program { static void Main(ssortingng[] args) { var names = Enumerable.Range(1, 10000).Select(i => i.ToSsortingng()).ToList(); var namesHash = new HashSet(names); ssortingng testName = "9999"; for (int i = 0; i < 10; i++) { Profiler.ReportRunningTimes(new Dictionary() { { "Enumerable.Any", () => ExecuteContains(names, testName, ContainsAny) }, { "ICollection.Contains", () => ExecuteContains(names, testName, ContainsCollection) }, { "Foreach Loop", () => ExecuteContains(names, testName, ContainsLoop) }, { "HashSet", () => ExecuteContains(namesHash, testName, ContainsCollection) } }, (s, ts) => Console.WriteLine("{0, 20}: {1}", s, ts), 10000); Console.WriteLine(); } Console.ReadLine(); } static bool ContainsAny(ICollection names, ssortingng name) { return names.Any(s => s == name); } static bool ContainsCollection(ICollection names, ssortingng name) { return names.Contains(name); } static bool ContainsLoop(ICollection names, ssortingng name) { foreach (var currentName in names) { if (currentName == name) return true; } return false; } static void ExecuteContains(ICollection names, ssortingng name, Func, ssortingng, bool> containsFunc) { if (containsFunc(names, name)) Trace.WriteLine("Found element in list."); } } 

Ne vous préoccupez pas des composants internes de la classe Profiler . Il lance simplement l’ Action dans une boucle et utilise un Stopwatch pour le chronométrer. Il s’assure également d’appeler GC.Collect() avant chaque test pour éliminer autant de bruit que possible.

Voici les résultats:

  Enumerable.Any: 00:00:03.4228475 ICollection.Contains: 00:00:01.5884240 Foreach Loop: 00:00:03.0360391 HashSet: 00:00:00.0016518 Enumerable.Any: 00:00:03.4037930 ICollection.Contains: 00:00:01.5918984 Foreach Loop: 00:00:03.0306881 HashSet: 00:00:00.0010133 Enumerable.Any: 00:00:03.4148203 ICollection.Contains: 00:00:01.5855388 Foreach Loop: 00:00:03.0279685 HashSet: 00:00:00.0010481 Enumerable.Any: 00:00:03.4101247 ICollection.Contains: 00:00:01.5842384 Foreach Loop: 00:00:03.0234608 HashSet: 00:00:00.0010258 Enumerable.Any: 00:00:03.4018359 ICollection.Contains: 00:00:01.5902487 Foreach Loop: 00:00:03.0312421 HashSet: 00:00:00.0010222 

Les données sont très cohérentes et racontent l’histoire suivante:

  • Naïvement utiliser la méthode Any extension est environ 9% plus lente que l’utilisation naïve d’une boucle foreach .

  • L’utilisation de la méthode la plus appropriée ( ICollection.Contains ) avec une structure de données non optimisée ( List ) est environ 50% plus rapide que l’utilisation d’une boucle foreach .

  • L’utilisation d’une structure de données optimisée ( HashSet ) HashSet complètement toutes les autres méthodes en termes de performances.

Je ne sais pas d’où vous avez 243%. Je suppose que cela a quelque chose à voir avec tout ce casting. Si vous utilisez une ArrayList non seulement vous utilisez une structure de données non optimisée, mais vous utilisez une structure de données largement obsolète .

Je peux prédire ce qui vient ensuite. “Ouais, je sais que vous pouvez mieux l’optimiser, mais ce n’était qu’un exemple pour comparer les performances de LINQ et non-LINQ.”

Ouais, mais si vous ne pouviez même pas être complet dans votre exemple, comment pouvez-vous vous attendre à être aussi complet en code de production?

La ligne de fond est la suivante:

La façon dont vous concevez et concevez votre logiciel est de manière exponentielle plus importante que les outils spécifiques que vous utilisez et à quel moment.

Si vous rencontrez des goulots d’étranglement dans les performances – ce qui risque tout autant de se produire avec LINQ que sans – alors résolvez-les. La suggestion d’Eric concernant des tests de performance automatisés est excellente. cela vous aidera à identifier rapidement les problèmes afin de pouvoir les résoudre correctement – pas en évitant un outil incroyable qui vous rend 80% plus productif mais qui subit une pénalité de performance inférieure à 10%, mais en enquêtant réellement sur le problème avec une vraie solution qui peut améliorer vos performances par un facteur de 2, 10, 100 ou plus.

Créer des applications performantes ne consiste pas à utiliser les bonnes bibliothèques. Il s’agit de profiler, de faire de bons choix de conception et de rédiger un bon code.

LINQ est-il un goulot d’étranglement réel (affectant la performance globale ou perçue de l’application)?

Votre application effectuera-t-elle cette opération sur 1 000 000 000 + enregistrements dans le monde réel? Si oui – alors vous pourriez envisager des alternatives – sinon c’est comme dire “on ne peut pas acheter cette berline familiale car elle ne roule pas bien à 180+ MPH”.

Si c’est “juste lent”, ce n’est pas une très bonne raison … par ce raisonnement, vous devriez écrire tout dans asm / C / C ++, et C # devrait être absent de la table pour être “trop ​​lent”.

Bien qu’une pessimisation prématurée soit (à mon humble avis) aussi mauvaise qu’une optimisation prématurée, vous ne devez pas exclure une technologie complète basée sur la vitesse absolue sans prendre en compte le contexte d’utilisation. Oui, si vous faites des calculs très lourds et que c’est un goulot d’étranglement , LINQ pourrait poser problème.

Un argument que vous pourriez utiliser en faveur de LINQ est que, bien que vous puissiez probablement le devancer avec du code manuscrit, la version LINQ pourrait probablement être plus claire et plus facile à entretenir.

Le problème avec ce genre de comparaison est que cela n’a pas de sens dans l’abstrait.

On pourrait battre l’un ou l’autre si on commençait par hacher les objects MyLinqTestClass1 par leur propriété Name. Entre ceux-ci si on pouvait les sortinger par Nom et faire ensuite une recherche binary. En effet, nous n’avons pas besoin de stocker les objects MyLinqTestClass1 pour cela, nous avons juste besoin de stocker les noms.

Taille de la mémoire un problème? Peut-être stocker les noms dans une structure DAWG, combiner suffit et ensuite l’utiliser pour cette vérification?

Est-ce que les coûts supplémentaires liés à la configuration de ces structures de données ont un sens? C’est impossible à dire.

Une autre question est un problème différent avec le concept de LINQ, qui est son nom. C’est génial à des fins de marketing pour MS de pouvoir dire “voici un tas de nouveaux trucs sympas qui fonctionnent ensemble” mais moins bons quand il s’agit de combiner des choses quand ils font le genre d’parsing où ils devraient les séparer . Vous avez un appel à Any qui implémente fondamentalement le modèle de filtre sur énumérable commun dans les jours .NET2.0 (et non inconnu avec .NET1.1 bien que ce soit plus difficile à écrire signifiait qu’il n’était utilisé que lorsque son efficacité dans certains cas vraiment important), vous avez des expressions lambda et vous avez des arbres de requêtes regroupés dans un même concept. Quel est le lent?

Je parie que la réponse ici est le lambda et non l’utilisation de Any , mais je ne parierais pas une grande quantité (par exemple le succès d’un projet), je testerais et soyez sûr. Pendant ce temps, la manière dont les expressions lambda fonctionnent avec IQueryable peut donner un code particulièrement efficace qu’il serait extrêmement difficile d’écrire avec une efficacité équivalente sans utiliser de lambdas.

Ne sums-nous pas efficaces lorsque LINQ est efficace sur le plan de l’efficacité parce qu’il a échoué à un benchmark artificiel? Je ne pense pas.

Utilisez LINQ là où cela a du sens.

Dans des conditions de goulot d’étranglement, éloignez-vous ou passez à LINQ malgré que cela semble approprié ou inapproprié en tant qu’optimisation. N’écrivez pas de code difficile à comprendre dès le départ, car vous ne feriez qu’une meilleure optimisation.

Pour moi, cela ressemble à un contrat et l’employeur ne comprend pas LINQ ou ne comprend pas les goulots d’étranglement du système. Si vous écrivez une application avec une interface graphique, l’impact mineur de l’utilisation de LINQ est négligeable. Dans une application graphique / Web classique, les appels en mémoire représentent moins de 1% du temps d’attente total. Vous, ou plutôt votre employeur, essayez d’optimiser ce 1%. Est-ce vraiment bénéfique?

Cependant, si vous écrivez une application scientifique ou fortement axée sur les mathématiques, avec un access très limité au disque ou à la firebase database, je conviens que LINQ n’est pas la solution.

BTW, le casting n’est pas nécessaire. Ce qui suit est fonctionnellement équivalent à votre premier test:

  for (int i = 0; i < 10000; i++) isInGroup = lst1.Any(item => item.Name == "9999"); 

Lorsque j’ai exécuté ceci en utilisant une liste de test contenant 10 000 objects MyLinqTestClass1, l’original s’est exécuté en 2,79 secondes, le révisé en 3,43 secondes. Économiser 30% sur les opérations qui absorbent probablement moins de 1% du temps CPU n’est pas une bonne utilisation de votre temps.

Peut-être que linq est lent, mais avec linq je peux paralléliser mon code très simplement.

Comme ça:

 lst1.Cast().AsParallel().Any(item => item.Name == "9999"); 

Comment vous paralléliseriez le cycle?

Voici une observation intéressante, étant donné que nHibernate est lent en raison de la lenteur de LINQ. Si vous faites LINQ to SQL (ou l’équivalent nHibernate), votre code LINQ se traduit par une requête EXISTS sur le serveur SQL où votre code de boucle doit d’abord récupérer toutes les lignes, puis les itérer. Maintenant, vous pouvez facilement écrire un tel test pour que le code de boucle lise une fois toutes les données (recherche dans une seule firebase database) pour toutes les exécutions de 10 Ko, mais le code LINQ exécute réellement des requêtes SQL de 10 Ko. Cela va probablement montrer un grand avantage en termes de vitesse pour la version en boucle qui n’existe pas dans la réalité. En réalité, une seule requête EXISTS surpassera chaque fois l’parsing et la boucle de la table – même si vous n’avez pas d’index sur la colonne interrogée (ce qui serait probablement le cas si cette requête est effectuée très souvent).

Je ne dis pas que c’est le cas avec votre test – nous n’avons pas assez de code pour le voir – mais ça pourrait l’être. Il se peut également que LINQ to Objects présente une différence de performances, mais cela ne se traduirait peut-être pas du tout par LINQ to SQL. Vous devez savoir ce que vous mesurez et comment il est applicable à vos besoins réels.

“On m’avait dit [par qui?] Que puisque .net linq est si lent [pour quoi?], Nous ne devrions pas l’utiliser”

Dans mon expérience, baser des décisions telles que la technique, la bibliothèque ou la langue à utiliser uniquement sur ce que quelqu’un vous a dit une fois est une mauvaise idée.

Tout d’abord, l’information provient-elle d’une source en laquelle vous avez confiance? Sinon, vous commettez une grave erreur en faisant confiance à cette personne (peut-être inconnue) pour prendre vos décisions en matière de conception. Deuxièmement, cette information est-elle toujours pertinente aujourd’hui? Mais d’accord, sur la base de votre benchmark simple et peu réaliste, vous avez conclu que LINQ est plus lent que l’exécution manuelle de la même opération. La question naturelle à se poser est la suivante: cette performance du code est-elle critique? Les performances de ce code seront-elles limitées par d’ autres facteurs que la vitesse d’exécution de ma requête LINQ – pensez aux requêtes de firebase database, à l’attente des E / S, etc.?

Voici comment j’aime travailler:

  1. Identifiez le problème à résoudre et rédigez la solution la plus simple et complète en fonction des exigences et des limites que vous connaissez déjà
  2. Déterminez si votre implémentation remplit réellement les conditions requirejses (est-ce assez rapide? La consommation de ressources est-elle maintenue à un niveau acceptable?).
  3. Si c’est le cas, vous avez terminé. Si ce n’est pas le cas, cherchez des moyens d’optimiser et d’affiner votre solution jusqu’à ce qu’elle réussisse le test à # 2. C’est là que vous devrez peut- être envisager d’abandonner quelque chose parce que c’est trop lent. Peut être. Les chances sont, cependant, que le goulot d’étranglement n’est pas du tout ce à quoi vous vous attendiez.

Pour moi, cette méthode simple a un objective unique: optimiser ma productivité en minimisant le temps passé à améliorer le code qui est déjà parfaitement adapté.

Oui, le jour viendra où vous constaterez que votre solution originale ne le coupe plus. Ou peut-être pas. Si c’est le cas, faites-le avec vous. Je vous suggère d’éviter de perdre votre temps à essayer de résoudre des problèmes hypothétiques (futurs).

Oui tu as raison. Il est facile d’écrire du code lent dans LINQ. Les autres ont également raison: il est facile d’écrire du code lent en C # sans LINQ.

J’ai écrit la même boucle que vous dans C et il a couru un certain nombre de millisecondes plus rapidement. La conclusion que j’en tire est que C # lui-même est lent.

Comme avec l’extension LINQ->, en C, il faut plus de 5 fois plus de lignes de code pour faire la même chose, ce qui ralentit l’écriture, est plus difficile à lire, est plus difficile à trouver et les corriger, mais si économiser quelques millisecondes pour chaque milliard d’itérations est important, c’est souvent ce qu’il faut.

Comme vous l’avez démontré, il est possible d’écrire du code non-LINQ plus performant que le code LINQ. Mais l’inverse est également possible. Compte tenu de l’avantage de maintenance que LINQ peut offrir, vous pouvez envisager de passer par défaut avec LINQ, car il est peu probable que vous renconsortingez des goulots d’étranglement de performance atsortingbuables à LINQ.

Cela dit, il existe des scénarios où LINQ ne fonctionnera pas. Par exemple, si vous importez une tonne de données, vous constaterez peut-être que l’exécution d’insertions individuelles est plus lente que l’envoi de données à SQL Server par lots XML. Dans cet exemple, l’insertion LINQ n’est pas plus rapide que l’insertion non-LINQ, mais il n’est pas nécessaire d’exécuter des insertions SQL individuelles pour les importations de données en masse.

Je préférerais dire que vous devriez éviter d’essayer trop fort pour écrire le code le plus efficace, sauf que c’est obligatoire.

Étant donné que LINQ est lent, il en résulterait également que PLINQ est lent et que NHibernate LINQ serait lent, de sorte que tout type sur l’instruction LINQ ne devrait pas être utilisé.

C’est un contexte différent, mais incroyablement différent. A 1,4 vs 5 secondes pour l’ensemble des 1 milliard d’opérations ne sont pas pertinentes lorsque vous parlez d’opérations d’access aux données.

Votre cas de test est un peu biaisé. L’opérateur ANY commencera à énumérer vos résultats et à retourner true à la première instance si find et quit. Essayez ceci avec des listes simples de chaînes pour voir le résultat. Pour répondre à votre question sur l’évitement de LINQ, vous devriez vraiment passer à l’utilisation de LINQ. Il facilite la lecture du code lors de requêtes complexes en plus de la vérification du temps de compilation. De plus, vous n’avez pas besoin d’utiliser l’opérateur Cast dans votre exemple.

 ssortingng compareMe = "Success"; ssortingng notEqual = "Not Success"; List headOfList = new List(); List midOfList = new List(); List endOfList = new List(); //Create a list of 999,999 items List masterList = new List(); masterList.AddRange(Enumerable.Repeat(notEqual, 999999)); //put the true case at the head of the list headOfList.Add(compareMe); headOfList.AddRange(masterList); //insert the true case in the middle of the list midOfList.AddRange(masterList); midOfList.Insert(masterList.Count/2, compareMe); //insert the true case at the tail of the list endOfList.AddRange(masterList); endOfList.Add(compareMe); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); headOfList.Any(p=>p == compareMe); stopWatch.ElapsedMilliseconds.Dump(); stopWatch.Reset(); stopWatch.Start(); midOfList.Any(p=>p == compareMe); stopWatch.ElapsedMilliseconds.Dump(); stopWatch.Reset(); stopWatch.Start(); endOfList.Any(p=>p == compareMe); stopWatch.ElapsedMilliseconds.Dump(); stopWatch.Stop(); 

The type casting is of course going to slow your code down. If you care that much, at least used a strongly typed IEnumerable for the comparison. I myself try to use LINQ wherever possible. It makes your code much more concise. It’s not not to have to worry about the imperative details of your code. LINQ is a functional concept, which means you’ll spell out what you want to happen and not worry about how.

There are a thousand times better reasons to avoid Linq.

Following quote from a discussion on Linq names a few of them:

QUOTE1

“For instance this works:

 var a = new { x = 1, y = 2 }; a = new { x = 1, y = 3 }; 

But this does not work:

 var a = new { x = 1, y = 2 }; a = new { x = 1, y = 2147483649 }; 

It returns : Error 1 Cannot implicitly convert type 'AnonymousType#1' to 'AnonymousType#2'

Mais cela fonctionne:

 var a = new { x = 1, y = 2147483648 }; a = new { x = 1, y = 2147483649 }; 

When you comstack:

 var a = new { x = 1, y = 2 }; 

The type of the x and y components is arbitrarily declared as a 32 bit signed integer, and it is one of the many integer types the platform has, without anything special.

But there is more. For instance this works:

 double x = 1.0; x = 1; 

But this does not work:

 var a = new { x = 1.0, y = 0 }; a = new { x = 1, y = 0 }; 

The numeric conversion rules are not applicable to this kind of types. As you can see, elegance is in every detail.”

QUOTE2

“It appears, then, that ‘AnonymousType#1’ and ‘AnonymousType#2’ are not synonymous–they name distinct types. And as { x = 1, y = 2 } and { y = 2, x = 1 } are expressions of those two types, respectively, not only do they denote distinct values, but also values of distinct types.

So, I was right to be paranoid. Now my paranoia extends even further and I have to ask what LinQ makes of the following comparison:

 new { x = 1, y = 2 } == new { x = 1, y = 2 } 

The result is false because this is a pointer comparison.

But the result of:

 (new { x = 1, y = 2 }).Equals(new { x = 1, y = 2 }) 

Est vrai.

And the result of:

 (new { x = 1, y = 2 }).Equals(new { y = 2, x = 1 }) 

et

 (new { x = 1, y = 2 }).Equals(new { a = 1, b = 2 }) 

Is false.”

QUOTE3

“updates are record oriented :-O

This, I agree, is problematic, and derives from LINQ’s sequence-oriented nature.

This is a show stopper for me. If I have to use SQL for my updates anyway why to bother about LinQ?

the optimization in LinQ to objects is unexistent.

There is not any algebraic optimization nor automatic expression rewrite. Many people don’t want to use LinQ to Objects because they lose a lot of performance. Queries are executed in the same way as you write them.”