7
votes

Améliorer la performance LINQ

J'ai une déclaration de Linq comme ceci: xxx

Ma question est la suivante: Combien de fois enregistre.sum (r => r.b) est calculé dans la dernière ligne? Est-ce que LINQ en boucle sur tous les enregistrements à chaque fois qu'il doit calculer une somme (dans ce cas, 3 somme () si boucle 3 fois)? Ou est-ce qu'il boucle de manière intelligente sur tous les enregistrements une fois que toutes les sommes à l'origine de toutes les sommes?


EDIT 1 :

  1. Je me demande s'il y a un moyen de l'améliorer en traversant seulement tout les enregistrements juste une fois (comme il suffit de le faire dans une seule boucle Lorsque vous utilisez une plaine pour la boucle)?

  2. et il n'est vraiment pas nécessaire de tout charger en mémoire avant Nous pouvons faire la somme et la moyenne . Nous pouvons sûrement résumer chaque élément en le chargeant du fichier. Y a-t-il un moyen de réduire la mémoire consommation aussi?


    EDIT 2

    Juste pour clarifier un peu, je n'ai pas utilisé Linq avant de finir comme ci-dessus. L'utilisation de plis tandis que / pour la boucle peut atteindre toutes les exigences de performance. Mais j'ai ensuite essayé d'améliorer la lisibilité et de réduire également les lignes du code à l'aide de LINQ. Il semble que nous ne puissions pas obtenir les deux en même temps.


2 commentaires

@Andywiesendanger, la requête ne semble pas être exécutée contre une base de données ...


Il s'exécutera trois fois (ou une fois, si le somme == 0 ), que ce soit contre une base de données ou non.


6 Réponses :


9
votes

deux fois, écrivez-le comme ceci et ce sera une fois:

var sum = records.Sum(r => r.b);

var avarage = sum != 0 ? records.Sum(r => r.a)/sum: 0;


2 commentaires

Vous êtes itération deux fois, pas une fois. Utilisez agrégat .


Hé, deux fois c'est toujours mieux que trois fois, Amirite?



0
votes

Somme obtient tous les enregistrements à tout moment que vous l'appelez, je vous recommande d'utiliser Tolist () -> Tolist ()?

var records = from line in myfile 
              let data = line.Split(',')
              select new { a=int.Parse(data[0]), b=int.Parse(data[1]) }.ToList();

var sumb = records.Sum(r => r.b);
var average = sumb !=0?records.Sum(r => r.a) / sumb :0;


0 commentaires

2
votes

Chaque appel à la méthode de somme itérale à travers toutes les lignes de myfile. Pour améliorer les performances, écrire: xxx

afin de créer la liste avec tous les éléments (avec des propriétés "A" et "B"), puis chaque appel à la méthode de somme sera itérer via cette liste sans fractionnement et analyse de données. Bien sûr, vous pouvez aller plus loin et vous rappeler le résultat de la méthode de somme dans une variable temporaire.


3 commentaires

"Pour améliorer les performances, écrire:" - Si cela exécute une base de données, cela vaste diminue les performances. En fonction de la mise en œuvre de l'énumérable, il peut également être plus lent tout en mémoire. Ajout d'un Tolist signifie que vous chargez toutes les lignes en mémoire, lorsque vous vous souciez que de la somme (code>. Et plus loin, je supposerais que somme est plus efficace lors de l'exécution de la base de données qu'il ne correspond à une liste <>


Mais cette requête ressemble à cela est contre le fichier! Cela améliorera donc les performances car il n'est pas possible d'invoquer la requête T-SQL contre le contenu du fichier.


Vous avez absolument raison - mon mauvais, j'ai enlevé le bowvote. Cela évaluera toujours la somme deux fois, mais c'est définitivement une amélioration de l'original.



4
votes

trois fois et ce que vous devez utiliser ici est agrégat code>, pas somme code>.

// do your original selection
var records = from line in myfile 
              let data = line.Split(',')
              select new { a=int.Parse(data[0]), b=int.Parse(data[1]) };
// aggregate them into one record
var sumRec = records.Aggregate((runningSum, next) =>
          { 
            runningSum.a += next.a;
            runningSum.b += next.b;                
            return runningSum;
          });
// Calculate your average
var average = sumRec.b != 0 ? sumRec.a / sumRec.b : 0;


6 commentaires

Merci, @flindeberg. Runningsum n'est-il pas lu que? Comment peut-on être assigné?


@james est correct. Les propriétés des types anonymes sont en lecture seule. Vous devez utiliser des variables locales ou créer un type fort. Sinon, c'est la réponse meilleure .


Cette solution a l'air assez efficace, mais je me demande si elle charge tout dans des disques avant de faire agréger? I.e. Il n'est vraiment pas nécessaire de charger tous les enregistrements en mémoire avant de pouvoir faire la somme et la moyenne.


@James cela dépend ... Reportez-vous à ma réponse.


@Cameron a totalement manqué l'effet en lecture seule des types anonymes, à la main à merveille la réponse = /


@james comme États de Cameron, cela dépend de la façon dont vous le chargez dans myfile .



1
votes

James, je ne suis pas un expert de tout écrou c'est mon idée. Je pense que cela peut être réduit à 1. Peut-être qu'il y a un peu plus de code. Les enregistrements sont toujours un iNeumable de Anonymouspe {int A, int b}.

* La dynamique était un moyen rapide de le résoudre. Vous devriez écrire une structure pour cela. xxx

pour d'autres structures, c'est simple. xxx

alors < Pré> xxx


2 commentaires

Merci beaucoup, @Petro. J'ai élu votre réponse bien que cela semble que pas tant d'autres personnes en ont voté. Je pense que c'est la solution la plus efficace jusqu'à présent, par rapport à toutes les autres réponses. Il ne nécessite pas tout ce qui est chargé dans la mémoire avant de faire la somme et qu'il ne boucle que sur tous les enregistrements une fois!


Je vous recommande également de créer écrire le Lambda en tant que fonction. Il n'a pas besoin de le créer à chaque fois que cela doit faire ce travail, plus Mor encore, une fonction privée à l'intérieur de la classe



6
votes

Il y a beaucoup forte> de réponses, mais aucun qui enveloppe toutes vos questions.

Combien de fois enregistre.sum (r => r.b) est calculé dans la dernière ligne? P> blockQuote>

trois fois. P>

linq boucle-t-il sur tous les enregistrements à chaque fois quand il doit calculer une somme (dans ce cas, 3 somme () si boucle 3 fois)? P> blockquote>

oui. p>

ou est-ce que cela boucle intelligemment sur tous les enregistrements qu'une fois à l'ordre tous les sommes? P> blockquote>

non. p>

Je me demande s'il y a un moyen de l'améliorer en traversant uniquement tous les enregistrements qu'une seule fois (comme il suffit de le faire en une seule boucle lorsque Utilisez une plaine pour la boucle)? P> blockQuote>

Vous pouvez le faire, mais cela vous oblige à charger avec impatience toutes les données qui contredisent votre prochaine question. P>

Et il n'y a vraiment pas besoin de tout charger en mémoire avant que nous peut faire la somme et la moyenne. Nous pouvons sûrement résumer chaque élément tout en le chargement du fichier. Y a-t-il un moyen de réduire la mémoire consommation aussi? P> BlockQuote>

C'est correct. Dans votre message d'origine, vous avez une variable appelée myfile code> et vous la mettez dans une variable locale appelée ligne code> (lecture: essentiellement un pour ). Puisque vous n'avez pas montré comment vous avez eu vos données code> myfile code>, je suppose que vous avez chargé avec impatience toutes les données. P>

Voici un exemple rapide de chargement paresseux de votre Données: P>

public IEnumerable<string> GetData()
{
    using (var fileStream = File.OpenRead(@"C:\Temp\MyData.txt"))
    {
        using (var streamReader = new StreamReader(fileStream))
        {
            string line;
            while ((line = streamReader.ReadLine()) != null)
            {                       
                yield return line;
            }
        }
    }
}

public void CalculateSumAndAverage()
{
    var sumA = 0;
    var sumB = 0;
    var average = 0;

    foreach (var line in GetData())
    {
        var split = line.Split(',');
        var a = Convert.ToInt32(split[0]);
        var b = Convert.ToInt32(split[1]);

        sumA += a;
        sumB += b;
    }

    // I'm not a big fan of ternary operators,
    // but feel free to convert this if you so desire.
    if (sumB != 0)
    {
        average = sumA / sumB;
    }
    else 
    {
        // This else clause is redundant, but I converted it from a ternary operator.
        average = 0;
    }
}


9 commentaires

@james purement une erreur logique commerciale plutôt qu'une erreur syntaxique. Cependant, je vais le corriger.


Merci, @cameron. C'est en fait ce que j'ai essayé en premier lieu .. J'ai ensuite essayé d'améliorer la lisibilité et de réduire également les lignes du code à l'aide de LINQ. Il semble que nous ne puissions pas obtenir les deux en même temps.


@James, je vous implorerais d'embrasser la simplicité sur la réduction des lignes de code. (Et personnellement, je pense que c'est plus lisible.) Juste parce que vous pouvez envelopper les choses en 3 lignes de Linq ne le font pas toujours la meilleure solution.


D'accord. Je pense que cela devrait être un problème commun et étant donné que Linq est un outil aussi mature, c'est pourquoi je m'attends à ce que les améliorations de performance puissent également être effectuées à l'aide de Linq. Je pense que la solution de Pedro Mora est très proche.


J'accepterai cette réponse car cela répond à la plupart de mes questions. Pour les personnes qui sont intéressées par une solution LINQ tout en conservant la performance de la même manière, veuillez vous reporter à la solution de Pedro Mora.


Vous pouvez utiliser fichier.readlines au lieu de votre fonction getdata .


@Codesinchaos hmm ... jusqu'à. Je ne m'attendais pas à ce que ce soit paresseux charger les lignes. J'aurais effectivement juré que c'est renvoyé string [] plutôt que ienumerable . Source


Readlines est paresseux et renvoie ienumerable , readalllines est désireux et renvoie chaîne []


@Codesinchaos, convenu. File.Readlines est la chargement paresseuse, mais dans la pratique, j'aurais toujours tendance à utiliser StreamReader tel qu'il peut permettre un accès partagé, par exemple. Le fichier ouvert par Excel ne peut pas être lu par les readlines ...