636 votes

Quelle méthode est la plus performante : .Any() ou .Count() > 0 ?

Dans le System.Linq nous pouvons maintenant étendre notre de IEnumerable pour avoir le Tout() y Compter() méthodes d'extension .

On m'a dit récemment que si je veux vérifier qu'une collection contient 1 ou plusieurs éléments, je dois utiliser la fonction .Any() au lieu de la méthode d'extension .Count() > 0 car la méthode d'extension .Count() La méthode d'extension doit itérer à travers tous les éléments.

Deuxièmement, certaines collections ont un propriété (pas une méthode d'extension) qui est Count ou Length . Serait-il préférable de les utiliser, au lieu de .Any() ou .Count() ?

yea / nae ?

0 votes

Il est préférable d'utiliser Any() sur les Enumérables et Count sur les Collections. Si quelqu'un pense qu'écrire '(somecollection.Count > 0)' sera source de confusion ou de problèmes de lisibilité, il vaut mieux l'écrire comme une méthode d'extension et la nommer Any(). Tout le monde sera alors satisfait. Du point de vue des performances et de la lisibilité. Ainsi, tout votre code sera cohérent et chaque développeur de votre projet ne devra pas s'inquiéter de choisir entre Count et Any.

2 votes

Vous avez vu Count() > 0 vs Any(), mais avez-vous vu Distinct().Count() > 1 vs Distinct().Skip(1).Any() ? Cette dernière est définitivement beaucoup plus rapide pour un grand nombre d'éléments où Count doit itérer sur l'ensemble pour obtenir un compte. Skip(1).Any() évite l'énumération complète. 100k itérations de la vérification d'un tableau de 1000 éléments avec des chaînes de 1 caractère qui s'exécute en environ 4000ms pour Count() > 1, s'exécute en seulement 20ms pour Skip(1).Any().

778voto

Marc Gravell Points 482669

Si vous commencez avec quelque chose qui a un .Length ou .Count (par exemple ICollection<T> , IList<T> , List<T> etc.), c'est l'option la plus rapide, puisqu'elle ne doit pas passer par le processus d'authentification de l'utilisateur. GetEnumerator() / MoveNext() / Dispose() séquence requise par Any() pour vérifier la présence d'un IEnumerable<T> séquence.

Pour juste IEnumerable<T> entonces Any() sera généralement est plus rapide, car elle ne doit examiner qu'une seule itération. Cependant, notez que l'implémentation LINQ-to-Objects de la fonction Count() vérifie la présence de ICollection<T> (en utilisant .Count comme une optimisation) - donc si votre source de données sous-jacente est directement une liste/collection, il n'y aura pas une grande différence. Ne me demandez pas pourquoi il n'utilise pas la fonction non générique ICollection ...

Bien sûr, si vous avez utilisé LINQ pour le filtrer, etc ( Where etc.), vous aurez une séquence basée sur des blocs d'itérateurs, et donc cette ICollection<T> l'optimisation est inutile.

En général, avec IEnumerable<T> : s'en tenir à Any() ;-p

12 votes

Marc : ICollection<T> ne dérive pas réellement de ICollection. J'ai été surpris aussi, mais Reflector ne ment pas.

7 votes

La mise en œuvre de Any() ne vérifie-t-elle pas l'interface ICollection et ne vérifie-t-elle pas ensuite la propriété Count ?

6 votes

Non, il ne l'est pas (après avoir vérifié le réflecteur).

66voto

kape123 Points 6369

Note : J'ai écrit cette réponse lorsque Entity Framework 4 était actuel. Le but de cette réponse n'était pas d'entrer dans les détails triviaux. .Any() vs .Count() test de performance. Le but était de signaler que EF est loin d'être parfait. Les versions plus récentes sont meilleures... mais si vous avez une partie de code qui est lente et qui utilise EF, testez avec TSQL direct et comparez les performances plutôt que de vous fier à des suppositions (qui ne sont pas toujours valables). .Any() est TOUJOURS plus rapide que .Count() > 0 ).


Bien que je sois d'accord avec la plupart des réponses et commentaires votés - en particulier sur le point suivant Any signaux intention du développeur mieux que Count() > 0 - J'ai eu une situation dans laquelle Count est plus rapide d'un ordre de grandeur sur SQL Server (EntityFramework 4).

Voici la requête avec Any que lew timeout exception (sur ~200.000 enregistrements) :

con = db.Contacts.
    Where(a => a.CompanyId == companyId && a.ContactStatusId <= (int) Const.ContactStatusEnum.Reactivated
        && !a.NewsletterLogs.Any(b => b.NewsletterLogTypeId == (int) Const.NewsletterLogTypeEnum.Unsubscr)
    ).OrderBy(a => a.ContactId).
    Skip(position - 1).
    Take(1).FirstOrDefault();

Count version exécutée en quelques millisecondes :

con = db.Contacts.
    Where(a => a.CompanyId == companyId && a.ContactStatusId <= (int) Const.ContactStatusEnum.Reactivated
        && a.NewsletterLogs.Count(b => b.NewsletterLogTypeId == (int) Const.NewsletterLogTypeEnum.Unsubscr) == 0
    ).OrderBy(a => a.ContactId).
    Skip(position - 1).
    Take(1).FirstOrDefault();

Je dois trouver un moyen de voir quel SQL exact les deux LINQs produisent - mais il est évident qu'il y a une énorme différence de performance entre les deux. Count y Any dans certains cas, et malheureusement, il semble que vous ne pouvez pas simplement vous en tenir à Any dans tous les cas.

EDIT : Voici les SQLs générés. Des merveilles comme vous pouvez le voir ;)

ANY :

exec sp\_executesql N'SELECT TOP (1) 
\[Project2\].\[ContactId\] AS \[ContactId\], 
\[Project2\].\[CompanyId\] AS \[CompanyId\], 
\[Project2\].\[ContactName\] AS \[ContactName\], 
\[Project2\].\[FullName\] AS \[FullName\], 
\[Project2\].\[ContactStatusId\] AS \[ContactStatusId\], 
\[Project2\].\[Created\] AS \[Created\]
FROM ( SELECT \[Project2\].\[ContactId\] AS \[ContactId\], \[Project2\].\[CompanyId\] AS \[CompanyId\], \[Project2\].\[ContactName\] AS \[ContactName\], \[Project2\].\[FullName\] AS \[FullName\], \[Project2\].\[ContactStatusId\] AS \[ContactStatusId\], \[Project2\].\[Created\] AS \[Created\], row\_number() OVER (ORDER BY \[Project2\].\[ContactId\] ASC) AS \[row\_number\]
    FROM ( SELECT 
        \[Extent1\].\[ContactId\] AS \[ContactId\], 
        \[Extent1\].\[CompanyId\] AS \[CompanyId\], 
        \[Extent1\].\[ContactName\] AS \[ContactName\], 
        \[Extent1\].\[FullName\] AS \[FullName\], 
        \[Extent1\].\[ContactStatusId\] AS \[ContactStatusId\], 
        \[Extent1\].\[Created\] AS \[Created\]
        FROM \[dbo\].\[Contact\] AS \[Extent1\]
        WHERE (\[Extent1\].\[CompanyId\] = @p\_\_linq\_\_0) AND (\[Extent1\].\[ContactStatusId\] <= 3) AND ( NOT EXISTS (SELECT 
            1 AS \[C1\]
            FROM \[dbo\].\[NewsletterLog\] AS \[Extent2\]
            WHERE (\[Extent1\].\[ContactId\] = \[Extent2\].\[ContactId\]) AND (6 = \[Extent2\].\[NewsletterLogTypeId\])
        ))
    )  AS \[Project2\]
)  AS \[Project2\]
WHERE \[Project2\].\[row\_number\] > 99
ORDER BY \[Project2\].\[ContactId\] ASC',N'@p\_\_linq\_\_0 int',@p\_\_linq\_\_0=4

COUNT :

exec sp\_executesql N'SELECT TOP (1) 
\[Project2\].\[ContactId\] AS \[ContactId\], 
\[Project2\].\[CompanyId\] AS \[CompanyId\], 
\[Project2\].\[ContactName\] AS \[ContactName\], 
\[Project2\].\[FullName\] AS \[FullName\], 
\[Project2\].\[ContactStatusId\] AS \[ContactStatusId\], 
\[Project2\].\[Created\] AS \[Created\]
FROM ( SELECT \[Project2\].\[ContactId\] AS \[ContactId\], \[Project2\].\[CompanyId\] AS \[CompanyId\], \[Project2\].\[ContactName\] AS \[ContactName\], \[Project2\].\[FullName\] AS \[FullName\], \[Project2\].\[ContactStatusId\] AS \[ContactStatusId\], \[Project2\].\[Created\] AS \[Created\], row\_number() OVER (ORDER BY \[Project2\].\[ContactId\] ASC) AS \[row\_number\]
    FROM ( SELECT 
        \[Project1\].\[ContactId\] AS \[ContactId\], 
        \[Project1\].\[CompanyId\] AS \[CompanyId\], 
        \[Project1\].\[ContactName\] AS \[ContactName\], 
        \[Project1\].\[FullName\] AS \[FullName\], 
        \[Project1\].\[ContactStatusId\] AS \[ContactStatusId\], 
        \[Project1\].\[Created\] AS \[Created\]
        FROM ( SELECT 
            \[Extent1\].\[ContactId\] AS \[ContactId\], 
            \[Extent1\].\[CompanyId\] AS \[CompanyId\], 
            \[Extent1\].\[ContactName\] AS \[ContactName\], 
            \[Extent1\].\[FullName\] AS \[FullName\], 
            \[Extent1\].\[ContactStatusId\] AS \[ContactStatusId\], 
            \[Extent1\].\[Created\] AS \[Created\], 
            (SELECT 
                COUNT(1) AS \[A1\]
                FROM \[dbo\].\[NewsletterLog\] AS \[Extent2\]
                WHERE (\[Extent1\].\[ContactId\] = \[Extent2\].\[ContactId\]) AND (6 = \[Extent2\].\[NewsletterLogTypeId\])) AS \[C1\]
            FROM \[dbo\].\[Contact\] AS \[Extent1\]
        )  AS \[Project1\]
        WHERE (\[Project1\].\[CompanyId\] = @p\_\_linq\_\_0) AND (\[Project1\].\[ContactStatusId\] <= 3) AND (0 = \[Project1\].\[C1\])
    )  AS \[Project2\]
)  AS \[Project2\]
WHERE \[Project2\].\[row\_number\] > 99
ORDER BY \[Project2\].\[ContactId\] ASC',N'@p\_\_linq\_\_0 int',@p\_\_linq\_\_0=4

Il semble que le Where pur avec EXISTS fonctionne beaucoup plus mal que le calcul de Count puis le Where avec Count == 0.

Faites-moi savoir si vous voyez une erreur dans mes conclusions. Ce que l'on peut retenir de tout ceci, indépendamment de la discussion sur le thème "Any vs Count", c'est que tout LINQ plus complexe est bien mieux réécrit en tant que procédure stockée ;).

2 votes

J'aimerais bien voir les plans de requête Sql générés par chaque linq-query pour chaque scénario.

48 votes

En me basant sur le SQL, tout ce que je peux dire c'est que les deux requêtes sont horribles. Je savais qu'il y avait une raison pour laquelle j'écris normalement mon propre TSQL...

0 votes

!Any devrait parcourir toutes les lignes comme le ferait Count. Le fait que votre exemple donne un résultat aussi horrible est un peu étrange, dans le pire des cas !Any ne devrait être qu'un peu plus lent que Count. Dans votre cas, je chercherais des moyens de simplifier la sélection, peut-être en la divisant en plusieurs étapes ou en réorganisant les conditions si cela est possible. Mais votre remarque selon laquelle la règle Any is better than Count ne s'applique pas à !Any is better than Count est très pertinente.

29voto

kamil-mrzyglod Points 3763

Comme il s'agit d'un sujet assez populaire et que les réponses diffèrent, je me devais de jeter un regard neuf sur le problème.

Test env : EF 6.1.3, serveur SQL, 300 000 enregistrements

Modèle de table :

class TestTable
{
    [Key]
    public int Id { get; set; }

    public string Name { get; set; }

    public string Surname { get; set; }
}

Code de test :

class Program
{
    static void Main()
    {
        using (var context = new TestContext())
        {
            context.Database.Log = Console.WriteLine;

            context.TestTables.Where(x => x.Surname.Contains("Surname")).Any(x => x.Id > 1000);
            context.TestTables.Where(x => x.Surname.Contains("Surname") && x.Name.Contains("Name")).Any(x => x.Id > 1000);
            context.TestTables.Where(x => x.Surname.Contains("Surname")).Count(x => x.Id > 1000);
            context.TestTables.Where(x => x.Surname.Contains("Surname") && x.Name.Contains("Name")).Count(x => x.Id > 1000);

            Console.ReadLine();
        }
    }
}

Résultats :

Any() ~ 3ms

Count() ~ 230ms pour la première requête, ~ 400ms pour la seconde

Remarques :

Dans mon cas, EF n'a pas généré de SQL comme @Ben l'a mentionné dans son post.

4 votes

Pour une comparaison correcte, vous devez faire Count() > 0 . :D

2 votes

Andrew, Count() > 0 ne va pas s'exécuter différemment de Count() dans ce test particulier.

19voto

gregmac Points 12813

Les détails exacts diffèrent un peu entre .NET Framework et .NET Core, mais cela dépend aussi un peu de ce que vous faites : si vous utilisez un fichier de type ICollection ou ICollection<T> (comme avec List<T> ), il existe un .Count dont l'accès est peu coûteux, alors que d'autres types peuvent nécessiter une énumération.

TL;DR :

Utilice .Count > 0 si le bien existe, et sinon .Any() .

Utilisation de .Count() > 0 es jamais la meilleure option, et dans certains cas, elle pourrait être beaucoup plus lente.

Cela s'applique à la fois à .NET Framework et à .NET Core.


Maintenant, nous pouvons plonger dans les détails

Listes et collections

Commençons par un cas très courant : l'utilisation de la fonction List<T> (qui est aussi ICollection<T> ).

El .Count est mis en œuvre comme :

    private int _size;

    public int Count {
        get {
            Contract.Ensures(Contract.Result<int>() >= 0);
            return _size; 
        }
    }

Ce que cela veut dire, c'est que _size est maintenu par Add() , Remove() etc, et puisqu'il s'agit simplement d'accéder à un champ, cette opération est extrêmement bon marché - nous n'avons pas besoin d'itérer sur les valeurs.

ICollection y ICollection<T> les deux ont .Count y le plus qui les mettent en œuvre sont susceptibles de le faire de manière similaire.

Autres IEnumerables

Tout autre IEnumerable qui ne sont pas aussi ICollection nécessitent une énumération de départ pour déterminer s'ils sont vides ou non. Le facteur clé affectant les performances est de savoir si nous finissons par énumérer un seul élément (idéal) ou la collection entière (relativement coûteux).

Si la collection provoque effectivement des entrées/sorties, par exemple en lisant une base de données ou un disque, les performances peuvent en souffrir.


Cadre .NET .Any()

Dans le .NET Framework (4.8), la fonction Any() la mise en œuvre est :

public static bool Any<TSource>(this IEnumerable<TSource> source) {
    if (source == null) throw Error.ArgumentNull("source");
    using (IEnumerator<TSource> e = source.GetEnumerator()) {
        if (e.MoveNext()) return true;
    }
    return false;
}

Cela signifie que quoi qu'il arrive, il va obtenir un nouvel objet énumérateur et essayer d'itérer une fois. Ceci est plus coûteux que d'appeler la méthode List<T>.Count mais au moins, il ne s'agit pas d'itérer la liste entière.

Cadre .NET .Count()

Dans le .NET Framework (4.8), la fonction Count() est (fondamentalement) :

public static int Count<TSource>(this IEnumerable<TSource> source)
{
    ICollection<TSource> collection = source as ICollection<TSource>;
    if (collection != null)
    { 
        return collection.Count;
    }
    int num = 0;
    using (IEnumerator<TSource> enumerator = source.GetEnumerator())
    {
        while (enumerator.MoveNext())
        {
            num = checked(num + 1);
        }
        return num;
    }
}

Si disponible, ICollection.Count est utilisé, mais sinon la collection est énumérée.


.NET Core .Any()

Le LINQ Any() dans .NET Core est beaucoup plus intelligente. Vous pouvez voir le source complète ici mais les éléments pertinents pour cette discussion :

    public static bool Any<TSource>(this IEnumerable<TSource> source)
    {
        //..snip..

        if (source is ICollection<TSource> collectionoft)
        {
            return collectionoft.Count != 0;
        }

        //..snip..

        using (IEnumerator<TSource> e = source.GetEnumerator())
        {
            return e.MoveNext();
        }
    }

Parce qu'un List<T> es un ICollection<T> ce qui appelle le Count (et bien qu'il appelle une autre méthode, il n'y a pas d'allocations supplémentaires).

.NET Core .Count()

L'implémentation .NET Core ( source ) est fondamentalement le même que celui de .NET Framework (voir plus haut), et il utilisera donc la fonction ICollection.Count si elle est disponible, et sinon, elle énumère la collection.


Résumé

Cadre .NET

  • Avec ICollection :

    • .Count > 0 est le meilleur
    • .Count() > 0 est bien, mais au final, il suffit d'appeler ICollection.Count
    • .Any() sera plus lent, car il énumère un seul élément.
  • Avec non ICollection (non .Count propriété)

    • .Any() est le meilleur, car il n'énumère qu'un seul élément.
    • .Count() > 0 est mauvais car il provoque une énumération complète

.NET Core

  • .Count > 0 est la meilleure solution, si elle est disponible ( ICollection )
  • .Any() est bien, et fera soit ICollection.Count > 0 ou énumérer un seul élément
  • .Count() > 0 est mauvais car il provoque une énumération complète

12voto

Ben Points 975

EDIT : ce problème a été corrigé dans la version 6.1.1 de EF et cette réponse n'est plus d'actualité.

Pour SQL Server et EF4-6, Count() est environ deux fois plus rapide que Any().

Lorsque vous exécutez Table.Any(), il générera quelque chose comme( alerte : ne vous blessez pas le cerveau en essayant de le comprendre )

SELECT 
CASE WHEN ( EXISTS (SELECT 
    1 AS [C1]
    FROM [Table] AS [Extent1]
)) THEN cast(1 as bit) WHEN ( NOT EXISTS (SELECT 
    1 AS [C1]
    FROM [Table] AS [Extent2]
)) THEN cast(0 as bit) END AS [C1]
FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]

qui nécessite 2 balayages de lignes avec votre condition.

Je n'aime pas écrire Count() > 0 parce que ça cache mon intention. Je préfère utiliser un prédicat personnalisé pour cela :

public static class QueryExtensions
{
    public static bool Exists<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
    {
        return source.Count(predicate) > 0;
    }
}

0 votes

J'ai aussi remarqué ceci. Le SQL Any() n'a pas de sens du tout. Je ne sais pas pourquoi ils ne le font pas : CASE WHEN(EXISTS(sql)) ALORS 1 ELSE 0 END. Je ne vois pas pourquoi ils ont besoin de faire un NOT EXISTS pour retourner 0.

0 votes

C'est faux. Vous avez trouvé un mauvais plan de requête par hasard. Cela arrive. N'importe lequel est, presque toujours, plus rapide.

0 votes

J'ai vérifié le sql généré dans 6.1.3, et je l'ai corrigé :SELECT CASE WHEN ( EXISTS (SELECT 1 AS [C1] FROM [dbo].[TestTables] AS [Extent1] WHERE [Extent1].[Id] > 1000 )) THEN cast(1 as bit) ELSE cast(0 as bit) END AS [C1] FROM ( SELECT 1 AS X ) AS [SingleRowTable1]

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X