L'ordre des fonctions LINQ est-il important?


114

Fondamentalement, comme l'indique la question ... l'ordre des fonctions LINQ est-il important en termes de performances ? Évidemment, les résultats devraient encore être identiques ...

Exemple:

myCollection.OrderBy(item => item.CreatedDate).Where(item => item.Code > 3);
myCollection.Where(item => item.Code > 3).OrderBy(item => item.CreatedDate);

Les deux me renvoient les mêmes résultats, mais sont dans un ordre LINQ différent. Je me rends compte que la réorganisation de certains éléments entraînera des résultats différents, et je ne suis pas préoccupé par ceux-ci. Ma principale préoccupation est de savoir si, en obtenant les mêmes résultats, la commande peut avoir un impact sur les performances. Et, pas seulement sur les 2 appels LINQ que j'ai passés (OrderBy, Where), mais sur tous les appels LINQ.


9
Super question.
Robert S.

Il est encore plus évident que l'optimisation du fournisseur compte avec un cas plus pédant comme var query = myCollection.OrderBy(item => item.Code).Where(item => item.Code == 3);.
Mark Hurd

1
Vous méritez un vote positif :), des questions intéressantes. Je l'examinerai lorsque j'écrirai mon Linq to Entities dans EF.
GibboK le

1
@GibboK: Soyez prudent lorsque vous essayez "d'optimiser" vos requêtes LINQ (voir la réponse ci-dessous). Parfois, vous n'optimisez rien. Il est préférable d'utiliser un outil de profilage lors d'une tentative d'optimisation.
myermian

Réponses:


147

Cela dépendra du fournisseur LINQ utilisé. Pour LINQ to Objects, cela pourrait certainement faire une énorme différence. Supposons que nous ayons réellement:

var query = myCollection.OrderBy(item => item.CreatedDate)
                        .Where(item => item.Code > 3);

var result = query.Last();

Cela nécessite que toute la collection soit triée puis filtrée. Si nous avions un million d'articles, dont un seul avait un code supérieur à 3, nous perdrions beaucoup de temps à commander des résultats qui seraient jetés.

Comparez cela avec l'opération inversée, en filtrant d'abord:

var query = myCollection.Where(item => item.Code > 3)
                        .OrderBy(item => item.CreatedDate);

var result = query.Last();

Cette fois, nous ne commandons que les résultats filtrés, ce qui dans le cas de l'exemple "un seul élément correspondant au filtre" sera beaucoup plus efficace - à la fois dans le temps et dans l'espace.

Cela peut également faire une différence dans l'exécution correcte ou non de la requête. Considérer:

var query = myCollection.Where(item => item.Code != 0)
                        .OrderBy(item => 10 / item.Code);

var result = query.Last();

C'est bien - nous savons que nous ne diviserons jamais par 0. Mais si nous effectuons le tri avant le filtrage, la requête lèvera une exception.


2
@Jon Skeet, Existe-t-il une documentation sur le Big-O pour chacun des fournisseurs et fonctions LINQ? Ou est-ce simplement un cas de "chaque expression est unique à la situation".
michael le

1
@michael: Ce n'est pas très clairement documenté, mais si vous lisez ma série de blogs "Edulinq", je pense que j'en parle assez en détail.
Jon Skeet


3
@gdoron: Ce que vous voulez dire n'est pas vraiment clair, pour être honnête. On dirait que vous voudrez peut-être écrire une nouvelle question. Gardez à l'esprit que Queryable n'essaie pas du tout d' interpréter votre requête - son travail consiste uniquement à préserver votre requête afin que quelque chose d'autre puisse l'interpréter. Notez également que LINQ to Objects n'utilise même pas d'arbres d'expression.
Jon Skeet

1
@gdoron: Le fait est que c'est le travail du fournisseur, pas celui de Queryable. Et cela ne devrait pas non plus avoir d'importance lors de l'utilisation d'Entity Framework. Il ne importe pour LINQ to Objects cependant. Mais oui, posez certainement une autre question.
Jon Skeet

17

Oui.

Mais exactement ce que la différence de performance est dépend de la façon dont l'arbre d'expression sous - jacente est évaluée par le fournisseur de LINQ.

Par exemple, votre requête peut s'exécuter plus rapidement la deuxième fois (avec la clause WHERE en premier) pour LINQ-to-XML, mais plus rapidement la première fois pour LINQ-to-SQL.

Pour connaître précisément la différence de performances, vous souhaiterez probablement profiler votre application. Comme toujours avec de telles choses, cependant, l'optimisation prématurée ne vaut généralement pas la peine - vous pouvez très bien constater que des problèmes autres que les performances LINQ sont plus importants.


5

Dans votre exemple particulier, cela peut faire une différence sur la performance.

Première requête: votre OrderByappel doit parcourir toute la séquence source, y compris les éléments dont la valeur Codeest égale ou inférieure à 3. La Whereclause doit alors également itérer toute la séquence ordonnée.

Deuxième requête: l' Whereappel limite la séquence aux seuls éléments où Codeest supérieur à 3. L' OrderByappel n'a alors besoin que de parcourir la séquence réduite renvoyée par l' Whereappel.


3

Dans Linq-To-Objects:

Le tri est plutôt lent et utilise de la O(n)mémoire. Whered'autre part est relativement rapide et utilise une mémoire constante. Donc, faire en Wherepremier sera plus rapide et beaucoup plus rapide pour les grandes collections.

La pression mémoire réduite peut également être importante, car les allocations sur le tas d'objets volumineux (ainsi que leur collection) sont relativement coûteuses d'après mon expérience.


1

Évidemment, les résultats devraient encore être identiques ...

Notez que ce n'est pas réellement vrai - en particulier, les deux lignes suivantes donneront des résultats différents (pour la plupart des fournisseurs / ensembles de données):

myCollection.OrderBy(o => o).Distinct();
myCollection.Distinct().OrderBy(o => o);

1
Non, ce que je voulais dire, c'est que les résultats devraient être identiques pour même envisager l'optimisation. Il ne sert à rien «d'optimiser» quelque chose et d'obtenir un résultat différent.
michael le

1

Il est intéressant de noter que vous devez être prudent lors de l' examen comment optimiser une requête LINQ. Par exemple, si vous utilisez la version déclarative de LINQ pour effectuer les opérations suivantes:

public class Record
{
    public string Name { get; set; }
    public double Score1 { get; set; }
    public double Score2 { get; set; }
}


var query = from record in Records
            order by ((record.Score1 + record.Score2) / 2) descending
            select new
                   {
                       Name = record.Name,
                       Average = ((record.Score1 + record.Score2) / 2)
                   };

Si, pour une raison quelconque, vous décidiez «d'optimiser» la requête en stockant d'abord la moyenne dans une variable, vous n'obtiendrez pas les résultats souhaités:

// The following two queries actually takes up more space and are slower
var query = from record in Records
            let average = ((record.Score1 + record.Score2) / 2)
            order by average descending
            select new
                   {
                       Name = record.Name,
                       Average = average
                   };

var query = from record in Records
            let average = ((record.Score1 + record.Score2) / 2)
            select new
                   {
                       Name = record.Name,
                       Average = average
                   }
            order by average descending;

Je sais que peu de gens utilisent LINQ déclaratif pour les objets, mais c'est une bonne matière à réflexion.


0

Cela dépend de la pertinence. Supposons que si vous avez très peu d'articles avec Code = 3, la commande suivante fonctionnera sur un petit ensemble de collections pour obtenir la commande par date.

Alors que si vous avez de nombreux articles avec la même date de création, la prochaine commande fonctionnera sur un plus grand ensemble de collections pour obtenir la commande par date.

Donc, dans les deux cas, il y aura une différence de performance

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.