J'essaie de me familiariser avec LINQ. Ce qui me dérange le plus, c'est que même si je comprends mieux la syntaxe, je ne veux pas sacrifier involontairement les performances pour l'expressivité.
Existe-t-il de bons répertoires centralisés d'informations ou de livres pour "LINQ efficace"? À défaut, quelle est votre technique LINQ haute performance préférée?
Je suis principalement concerné par LINQ to Objects, mais toutes les suggestions sur LINQ to SQL et LINQ to XML sont également les bienvenues. Merci.
La simple compréhension de ce que fait LINQ en interne devrait fournir suffisamment d'informations pour savoir si vous prenez un coup de performance.
Voici un exemple simple où LINQ améliore les performances. Considérez cette approche typique de la vieille école:
List<Foo> foos = GetSomeFoos();
List<Foo> filteredFoos = new List<Foo>();
foreach(Foo foo in foos)
{
if(foo.SomeProperty == "somevalue")
{
filteredFoos.Add(foo);
}
}
myRepeater.DataSource = filteredFoos;
myRepeater.DataBind();
Ainsi, le code ci-dessus va itérer deux fois et allouer un deuxième conteneur pour contenir les valeurs filtrées. Quel gâchis! Comparer avec:
var foos = GetSomeFoos();
var filteredFoos = foos.Where(foo => foo.SomeProperty == "somevalue");
myRepeater.DataSource = filteredFoos;
myRepeater.DataBind();
Cela ne répète qu'une fois (lorsque le répéteur est lié); il n'utilise que le conteneur d'origine; filteredFoos
n'est qu'un énumérateur intermédiaire. Et si, pour une raison quelconque, vous décidez de ne pas lier le répéteur plus tard, rien n'est perdu. Vous ne répétez ou n'évaluez même pas une seule fois.
Lorsque vous vous lancez dans des manipulations de séquences très complexes, vous pouvez potentiellement gagner beaucoup en tirant parti de l'utilisation inhérente de LINQ du chaînage et de l'évaluation paresseuse. Encore une fois, comme pour tout, il s'agit simplement de comprendre ce qu'il fait réellement.
Linq, en tant que technologie intégrée, présente des avantages et des inconvénients en termes de performances. L'équipe de .NET a accordé une attention considérable aux performances du code derrière les méthodes d'extension, et sa capacité à fournir une évaluation paresseuse signifie que le coût de la plupart des manipulations sur un ensemble d'objets est réparti sur l'algorithme plus large nécessitant l'ensemble manipulé. . Cependant, il y a certaines choses que vous devez savoir qui peuvent faire ou défaire les performances de votre code.
D'abord et avant tout, Linq ne sauvegarde pas comme par magie votre programme le temps ou la mémoire nécessaire pour effectuer une opération; cela peut simplement retarder ces opérations jusqu'à ce qu'elles soient absolument nécessaires. OrderBy () exécute un QuickSort, qui prendra le temps de reconnaissance comme si vous aviez écrit votre propre QuickSorter ou utilisé List.Sort () au bon moment. Donc, soyez toujours conscient de ce que vous demandez à Linq de faire pour une série lors de l'écriture de requêtes; si une manipulation n'est pas nécessaire, cherchez à restructurer la chaîne de requête ou de méthode pour l'éviter.
De même, certaines opérations (tri, regroupement, agrégats) nécessitent la connaissance de l'ensemble sur lequel elles agissent. Le tout dernier élément d'une série pourrait être le premier que l'opération doit renvoyer de son itérateur. En plus de cela, parce que les opérations Linq ne devraient pas modifier leur source énumérable, mais la plupart des algorithmes qu'ils utilisent le feront (c.-à-d. Les tris sur place), ces opérations finissent non seulement par évaluer, mais par copier l'ensemble de l'énumérable dans une structure concrète et finie. , en effectuant l'opération et en la cédant. Ainsi, lorsque vous utilisez OrderBy () dans une instruction et que vous demandez un élément du résultat final, TOUT ce que l'IEnumerable qui lui est donné peut produire est évalué, stocké en mémoire sous forme de tableau, trié, puis renvoyé un élément à un temps. La morale est que toute opération qui nécessite un ensemble fini au lieu d'un énumérable doit être placée aussi tard que possible dans la requête, ce qui permet d'autres opérations comme Where () et Select () pour réduire la cardinalité et l'empreinte mémoire de l'ensemble source.
Enfin, les méthodes Linq augmentent considérablement la taille de la pile d'appels et l'empreinte mémoire de votre système. Chaque opération qui doit connaître l'ensemble complet conserve l'ensemble source complet en mémoire jusqu'à ce que le dernier élément ait été itéré, et l'évaluation de chaque élément impliquera une pile d'appels au moins deux fois plus profonde que le nombre de méthodes dans votre chaîne ou clauses dans votre instruction inline (un appel à MoveNext () de chaque itérateur ou donnant GetEnumerator, plus au moins un appel à chaque lambda en cours de route). Cela va simplement entraîner un algorithme plus grand et plus lent qu'un algorithme en ligne conçu intelligemment qui effectue les mêmes manipulations. Le principal avantage de Linq est la simplicité du code. Créer, puis trier, un dictionnaire de listes de valeurs de groupes n'est pas un code très facile à comprendre (croyez-moi). Les micro-optimisations peuvent l'obscurcir davantage. Si la performance est votre principale préoccupation, n'utilisez pas Linq; cela ajoutera environ 10% de temps supplémentaire et plusieurs fois plus de mémoire pour manipuler une liste sur place vous-même. Cependant, la maintenabilité est généralement la principale préoccupation des développeurs, et Linq aide définitivement.
Sur le coup de la performance: si la performance de votre algorithme est la première priorité sacrée et sans compromis, vous programmeriez dans un langage non géré comme C++; .NET va être beaucoup plus lent du fait qu'il s'agit d'un environnement d'exécution géré, avec une compilation native JIT, de la mémoire gérée et des threads système supplémentaires. J'adopterais une philosophie selon laquelle il serait "assez bon"; Linq peut introduire des ralentissements de par sa nature, mais si vous ne pouvez pas faire la différence et que votre client ne peut pas faire la différence, à toutes fins pratiques, il n'y a pas de différence. "L'optimisation prématurée est la racine de tout Mal"; Faites-le fonctionner, PUIS cherchez des occasions de le rendre plus performant, jusqu'à ce que vous et votre client conveniez qu'il est assez bon. Cela pourrait toujours être "mieux", mais à moins que vous ne vouliez être un code machine à emballer à la main, vous trouverez un point en deçà de celui où vous pouvez déclarer la victoire et passer à autre chose.
Il existe différents facteurs qui affecteront les performances.
Souvent, le développement d'une solution à l'aide de LINQ offre des performances assez raisonnables car le système peut créer une arborescence d'expressions pour représenter la requête sans réellement exécuter la requête pendant qu'il la construit. Ce n'est que lorsque vous parcourez les résultats qu'il utilise cette arborescence d'expression pour générer et exécuter une requête.
En termes d'efficacité absolue, en exécutant contre des procédures stockées prédéfinies, vous pouvez voir une baisse des performances, mais généralement l'approche à adopter consiste à développer une solution en utilisant un système qui offre des performances raisonnables (comme LINQ), et ne vous inquiétez pas de quelques pour cent de perte de performance. Si une requête s'exécute alors lentement, alors vous envisagez peut-être l'optimisation.
La réalité est que la majorité des requêtes n'auront pas le moindre problème à se faire via LINQ. L'autre fait est que si votre requête s'exécute lentement, il y a probablement plus de problèmes avec l'indexation, la structure, etc. qu'avec la requête elle-même, donc même lorsque vous cherchez à optimiser des choses, vous ne toucherez souvent pas le LINQ, juste le structure de base de données contre laquelle il travaille.
Pour gérer XML, si vous avez un document en cours de chargement et d'analyse en mémoire (comme tout ce qui est basé sur le modèle DOM, ou un XmlDocument ou autre), vous obtiendrez plus d'utilisation de la mémoire que les systèmes qui font quelque chose comme déclencher des événements vers indiquer de trouver une balise de début ou de fin, mais pas de construire une version complète en mémoire du document (comme SAX ou XmlReader). L'inconvénient est que le traitement basé sur les événements est généralement plus complexe. Encore une fois, avec la plupart des documents, il n'y aura pas de problème - la plupart des systèmes ont plusieurs Go de RAM, donc prendre quelques Mo représentant un seul document XML n'est pas un problème (et vous traitez souvent un grand ensemble de documents XML au moins quelque peu séquentiellement). Ce n'est que si vous avez un énorme fichier XML qui prendrait des centaines de Mo que vous vous inquiétez du choix particulier.
Gardez à l'esprit que LINQ vous permet d'itérer sur les listes en mémoire et ainsi de suite, donc dans certaines situations (comme lorsque vous allez utiliser un ensemble de résultats encore et encore dans une fonction), vous pouvez utiliser .ToList ou .ToArray pour renvoyer les résultats. Parfois, cela peut être utile, bien que vous souhaitiez généralement utiliser les requêtes de la base de données plutôt en mémoire.
Quant aux favoris personnels - NHibernate LINQ - c'est un outil de mappage relationnel objet qui vous permet de définir des classes, de définir des détails de mappage, puis de le faire générer la base de données à partir de vos classes plutôt que l'inverse, et le support LINQ est assez bon (certainement mieux que les goûts de SubSonic).
Il y a un projet codeplex appelé i4o que j'ai utilisé il y a quelque temps qui peut aider à améliorer les performances de Linq to Objects dans les cas où vous faites des comparaisons d'égalité, par exemple.
from p in People
where p.Age == 21
select p;
http://i4o.codeplex.com/ Je ne l'ai pas testé avec .Net 4, je ne peux donc pas dire en toute sécurité que cela fonctionnera mais vaut la peine d'être vérifié. Pour qu'elle fonctionne, il vous suffit principalement de décorer votre classe avec certains attributs pour spécifier quelle propriété doit être indexée. Quand je l'ai utilisé auparavant, cela ne fonctionne qu'avec des comparaisons d'égalité.
Dans linq to SQL, vous n'avez pas besoin de vous soucier autant des performances. vous pouvez enchaîner toutes vos déclarations de la manière la plus lisible. Linq traduit simplement toutes vos instructions en 1 instruction SQL à la fin, qui n'est appelée/exécutée qu'à la fin (comme lorsque vous appelez une .ToList()
un var
peut contenir cette instruction sans l'exécuter si vous souhaitez appliquer diverses instructions supplémentaires dans différentes conditions. L'exécution à la fin ne se produit que lorsque vous souhaitez traduire vos instructions en un résultat comme un objet ou une liste d'objets.