2010-09-20 5 views
1

J'ai créé une requête LINQ unique qui crée un groupe principal, puis deux groupes imbriqués. Dans le dernier nid, il y a aussi un OrderBy simple. Le problème que je rencontre est lors de l'écriture de la requête ou en essayant de l'éditer, la charge de mémoire de la mémoire du studio visuel est de ~ 500 Mo et elle consomme 50% de mon processeur, ce qui rend le studio visuel inerte pendant quelques minutes. Si je commente la requête alors Visual Studio agit très bien. Donc ma question est pourquoi le studio visuel consomme-t-il autant de mémoire pendant la conception pour une requête linq, à condition que ce soit compliqué?Utilisation de la mémoire par LINQ et Visual Studio 2010

datatable J'utilise est 10732 lignes de long par 21 colonnes à travers

var results = from p in m_Scores.AsEnumerable() 
     group p by p.Field<string>("name") into x 
     select new 
     { 
     Name = x.Key, 
     Members = from z in x 
      group z by z.Field<string>("id") into zz 
      select new 
       { 
       Id = zz.Key, 
       Plots = from a in zz 
        group a by a.Field<string>("foo") into bb 
        select new 
        { 
         Foo = bb.Key, 
         Bars = bb 
        }.Bars.OrderBy(m => m.Field<string>("foo")) 
       } 
     }; 

Spécifications matérielles:

Dell Latitude avec un processeur de base 2,20 GHz double et 4 Go de RAM

+3

Veuillez publier votre requête LINQ pour tout le monde, ainsi que la taille approximative des collections sur lesquelles elle fonctionne. – Dave

+0

Triple imbriqué «groupe par» peut être le problème ici - même si je ne peux jamais sembler comprendre le «langage naturel» LINQ. –

+0

Vous devriez probablement nous montrer quelques spécifications matérielles aussi ... –

Répondre

2

Le problème avec groupes et ordres est qu'ils nécessitent la connaissance de l'ensemble de la collection afin d'effectuer les opérations. Même chose avec les agrégats comme min, max, sum, avg, etc. Certaines de ces opérations ne peuvent pas interroger le vrai type de l'IEnumerable étant passé, ou cela n'aurait pas d'importance, car elles sont de nature "destructrice" donc elles doivent créer une copie de travail. Quand vous enchaînez ces choses ensemble, vous vous retrouvez avec au moins deux copies de l'énumérable complet; celui produit par la méthode précédente qui est itéré par la méthode actuelle, et celui qui est généré par la méthode actuelle. Toute copie de l'énumérable qui a une référence en dehors de la requête (comme la source enumerable) reste également en mémoire, et les énumératifs qui sont devenus orphelins restent en place jusqu'à ce que le thread GC ait le temps de se débarrasser et de les finaliser. Pour une grande source enumerable, tout cela peut créer une demande massive sur le tas.

De plus, l'agrégation d'agrégats dans les clauses peut rendre très rapidement la requête coûteuse. Contrairement à un SGBD qui peut créer un «plan de requête», Linq n'est pas très intelligent. Min(), par exemple, requiert l'itération de l'énumération entière pour trouver la plus petite valeur de la projection spécifiée. Lorsque c'est un critère d'une clause Where, un bon SGBD trouvera cette valeur une fois par contexte, puis ajoutera la valeur dans les évaluations ultérieures si nécessaire. Linq exécute simplement la méthode d'extension chaque fois qu'elle est appelée, et quand vous avez une condition comme enumerable.Where (x => x.Value == enumerable.Min (x2 => x2.Value)), c'est un O (N^2) - opération de complexité juste pour évaluer le filtre. Ajoutez plusieurs niveaux de groupement et le Big-O peut facilement atteindre une complexité élevée.

Généralement, vous pouvez réduire le temps de requête en effectuant les optimisations qu'un SGBD donnerait à la même requête. Si la valeur d'un agrégat peut être connue pour la portée complète de la requête (par exemple result = source.Where(s=>s.Value == source.Min(x=>x.value))), évaluez-la dans une variable à l'aide de la clause let (ou d'une requête externe) et remplacez les appels Min() par l'alias. L'itération de l'énumérable deux fois est généralement moins coûteuse que l'itération N^2 fois, surtout si l'énumérable reste en mémoire entre les itérations. En outre, assurez-vous que l'ordre de votre requête réduit l'espace d'échantillonnage autant et aussi peu cher que possible avant de commencer le regroupement. Vous pouvez être en mesure de faire des suppositions éclairées sur les conditions qui doivent être évaluées de manière coûteuse, telles que Où (s => s.Valeur < seuil) .Où (s => s.Value == source.Min (x => x.Value)) ou plus concis, Où (s => s.Valeur < seuil & & s.Valeur == source.Min (x => x.Valeur)) (la seconde fonctionne en C# en raison de l'évaluation de la condition paresseuse, mais pas tous les langues évaluent paresseusement). Cela réduit le nombre d'évaluations de Min() au nombre d'éléments répondant aux premiers critères. Vous pouvez utiliser les critères existants pour faire la même chose, où les critères A et B sont suffisamment indépendants pour que A & & B == B & & A.

+0

Même dans la conception et l'exécution cela arrive. Je comprends comment cela peut affecter les performances d'exécution mais pas le temps de conception. – Nathan

+0

En temps de conception, il se passe beaucoup de choses dans les coulisses pendant que vous tapez. Les fonctionnalités "check-as-you-go" de VS/ReSharper effectuent diverses analyses du code; par exemple, ReSharper vérifie si les conditions sont toujours vraies ou toujours fausses. Certains d'entre eux nécessitent une analyse du chemin d'exécution, qui sera au moins quelque peu liée à la complexité de l'algorithme lui-même. – KeithS

+0

Ah ok, ça a du sens maintenant. Merci pour la leçon! – Nathan

Questions connexes