2009-03-18 13 views
3

Lors de la construction d'expressions LINQ (pour moi, linq aux objets) il y a plusieurs façons d'accomplir quelque chose, certaines beaucoup, beaucoup mieux et plus efficace que d'autres.Quelle est la meilleure façon d'optimiser ou de "régler" les expressions LINQ?

  • Existe-t-il un bon moyen de "régler" ou d'optimiser ces expressions?
  • Quelles sont les métriques fondamentales utilisées par les gens et comment les rassemblez-vous?
  • Existe-t-il un moyen d'obtenir le nombre "total d'itérations" ou une autre métrique, où vous pourriez "savoir" que les moyens inférieurs sont meilleurs?

EDIT

Merci Richard/Jon pour vos réponses. Ce que je veux vraiment, c'est un moyen d'obtenir un simple compte d'opérations "OCount" pour une expression LINQ bien que je ne suis pas sûr que les hooks existent dans LINQ pour le permettre. Supposons que j'ai un niveau de performance cible pour un matériel de machine spécifique (un SLA). Idéalement, j'ajouterais un test unitaire pour confirmer que les données typiques déplacées dans cette requête seraient traitées dans le temps imparti (à partir du SLA). Le problème est que ceci serait exécuté sur le serveur de construction/la machine de développement/etc. qui ressemble probablement peu au matériel de la machine du SLA. Donc l'idée est que je déterminerais un "OCount" max acceptable pour l'expression, sachant que si l'OCount est inférieur à X, il fournira certainement une performance acceptable sous le SLA sur le matériel "typique" cible. Si l'OCount dépasse ce seuil, le test build/unit génère un avertissement. Idéalement, je voudrais avoir quelque chose comme ça (pseudocode-ish):

var results = [big linq expression run against test dataset]; 
Assert.IsLess(MAXALLOWABLE_OCOUNT, results.OCount) 

où results.OCount serait tout simplement me donner les itérations au total (n) nécessaires pour produire le jeu de résultats.

Pourquoi voudrais-je cela? Eh bien, même avec une expression LINQ de taille modérée, un petit changement/ajout peut avoir des effets ÉNORMES sur la performance en raison de l'augmentation du nombre total d'opérations. Le code d'application passerait toujours tous les tests unitaires car il produirait toujours le résultat correct, mais travaillerait misérablement lentement lorsqu'il est déployé.

L'autre raison est pour l'apprentissage simple. Si vous faites quelque chose et que le nombre monte ou descend d'un ordre de grandeur, alors vous apprenez quelque chose.

EDIT # 2 Je vais jeter dans une réponse potentielle aussi bien. Ce n'est pas le mien, ça vient de Cameron MacFarland d'une autre question que j'ai posée qui a engendré celle-ci. Il s'avère, je pense que la réponse à celui-ci pourrait fonctionner ici dans un environnement de test unitaire comme celui que j'ai décrit dans le premier edit de cette question. L'essentiel serait de créer les jeux de données de test dans l'unité de test unitaire que vous insérez dans l'expression LINQ de la manière décrite dans cette réponse, puis additionnez les comptes d'itération et comparez au nombre d'itérations maximum autorisé.

Voir Cameron's answer here

Répondre

3
  1. obtenir un SLA (ou toute autre définition) décrivant la performance globale requise.

  2. Mesurez la performance des applications, et à quel point elles sont inférieures aux exigences (si elles sont conformes aux exigences, arrêtez-vous et faites quelque chose d'utile). Utilisez un profileur pour obtenir une ventilation détaillée des performances, identifier les parties du système les plus susceptibles d'être améliorées (apporter une petite amélioration au code chaud est susceptible d'être meilleure qu'une grande amélioration par rapport au code rarement appelé).

  3. Effectuez le changement, réexécutez les tests unitaires/fonctionnels (aucun point ne fait le mauvais).

  4. Aller à 1.

Si, # 3, vous trouvez une expression LINQ est un problème de performamce puis commencer à penser à avoir besoin d'une réponse à cette question. La réponse dépendra complètement du fournisseur LINQ que vous utilisez et des détails de son utilisation dans votre cas. Il n'y a pas de réponse générale.

6

Vous devez essentiellement travailler sur la fonction de complexité. Cela dépend de l'opérateur, mais n'a pas tendance à être très bien documenté, malheureusement.

(Pour le principe général Je suis d'accord avec la réponse de Richard - c'est juste LINQ à farcir des objets.)

Si vous avez des opérateurs spécifiques qui vous intéressent, il serait intéressant de demander à leur sujet, mais au large de la haut de ma tête:

  • Select = O (n)
  • Où = O (n)
  • Joindre = O (intérieur + correspond extérieur +) (il n'y a pas moins cher que inner + outer, mais pourrait être aussi mauvais que inner * outer selon le th Résultats e)
  • GroupJoin = identique à rejoindre, mais mises en mémoire tampon à la place de la transmission en continu externe
  • TriPar = O (n log n)
  • SelectMany = O (n + résultats)
  • Count = O (1) ou O (n) selon que l'on met en œuvre IList
  • Count (prédicat) = O (n)
  • Max/Min = O (n)
  • Tous/Tous = O (n) (avec possible au début des
  • Distinct = O (n)
  • Ignorer/Ne = O (n)
  • SkipWhile/TakeWhile = O (n)

Les caractéristiques exactes dépendent du fait que les tampons de l'opérateur ou les cours d'eau aussi.

0

Ajout sur Jon qui ajoute sur Richard

Une autre question à considérer est de savoir si vous ne traitez pas tous les résultats d'une requête LINQ. Dans certaines conditions, en particulier l'interface utilisateur, vous finissez seulement par traiter un sous-ensemble des résultats renvoyés par une requête LINQ. Dans ces situations, il est important de savoir quelles requêtes LINQ prennent en charge l'évaluation paresseuse. C'est la capacité de retourner un sous-ensemble des résultats sans traiter toute la collection.

Par exemple appeler MoveNext() sur les opérations LINQ suivantes traitera un résultat à la fois

  • Sélectionnez

Mais les conditions suivantes doivent traiter tous les éléments de la collection avant renvoyer un seul article.

  • TriPar
  • Sauf (procédés de l'autre collection entièrement)