Qual método tem melhor desempenho: .Any () vs .Count ()> 0?


578

no System.Linqespaço para nome, agora podemos estender nossos IEnumerable para ter os métodos de extensão Any () e Count () .

Foi-me dito recentemente que, se eu quiser verificar se uma coleção contém 1 ou mais itens, devo usar o .Any()método de extensão em vez do método de .Count() > 0extensão, porque o .Count()método de extensão precisa percorrer todos os itens.

Em segundo lugar, algumas coleções têm uma propriedade (não um método de extensão) que é Countou Length. Seria melhor usá-los, em vez de .Any()ou .Count()?

sim / não?


Melhor usar Any () em Enumerables e Contar em Coleções. Se alguém sentir que escrever '(alguma coleta. Contagem> 0)' irá confundir ou causar problemas de legibilidade, escreva-a como método de extensão como Any (). Então todo mundo satisfeito. Em termos de desempenho e de legibilidade. Para que todo o seu código tenha consistência e o desenvolvedor individual do seu projeto não precise se preocupar em escolher Contar x Qualquer.
— Mahesh Bongani

Respostas:


709

Se você está começando com algo que tem um .Lengthou .Count(como ICollection<T>, IList<T>, List<T>, etc.) - então esta será a opção mais rápida, uma vez que não precisa passar pelo GetEnumerator()/ MoveNext()/ Dispose()seqüência necessária por Any()para verificar se há um não-vazia IEnumerable<T>seqüência .

Por apenas IEnumerable<T>, em seguida, Any()irá geralmente ser mais rápido, uma vez que só tem de olhar para uma iteração. No entanto, observe que a implementação do LINQ-to-Objects Count()verifica ICollection<T>(usando .Countcomo uma otimização) - portanto, se a fonte de dados subjacente for diretamente uma lista / coleção, não haverá uma grande diferença. Não me pergunte por que não usa o não genérico ICollection...

Obviamente, se você usou o LINQ para filtrá-lo, etc ( Whereetc), terá uma sequência baseada em bloco iterador e, portanto, essa ICollection<T>otimização é inútil.

Em geral com IEnumerable<T>: fique com Any();-p


9
Marc: ICollection <T> na verdade não deriva de ICollection. Também fiquei surpresa, mas Reflector não mente.
— 22711 Bryan Watts

7
Nenhuma implementação () verifica a interface ICollection e verifica a propriedade Count?
— derigel

313
Eu acho que há outra razão para usar Any () na maioria das vezes. Sinaliza a intenção precisa do desenvolvedor. Se você não está interessado em saber o número de itens, mas apenas se houver algum, então somecollection.Any () é mais simples e mais clara do que somecollection.Count> 0
— TJKjaer

13
@huttelihut - Quantos desenvolvedores você conhece genuinamente confusos com a declaração (somecollection.Count > 0)? Todo o nosso código antes da introdução do método .Any () do LINQ era difícil de entender?
— CraigTP

25
@JLRishe - Ainda sinto que someCollection.Count > 0é tão claro quanto someCollection.Any()e tem o benefício adicional de maior desempenho e de não exigir o LINQ. Concedido, este é um caso muito simples e outras construções que usam operadores LINQ transmitirão a intenção dos desenvolvedores muito mais clara que a opção equivalente não-LINQ.
— CraigTP

65

Nota: Eu escrevi esta resposta quando o Entity Framework 4 era real. O objetivo desta resposta não era entrar em testes triviais .Any()versus .Count()testes de desempenho. O objetivo era sinalizar que a EF está longe de ser perfeita. Versões mais recentes são melhores ... mas se você tiver parte do código que é lenta e usa EF, teste com TSQL direto e compare o desempenho em vez de confiar em suposições (que .Any()é SEMPRE mais rápida que .Count() > 0).


Embora eu concorde com a maioria das respostas e comentários votados - especialmente sobre a intenção dos desenvolvedores deAny sinais pontuais melhor do que -, tive uma situação em que Count é mais rápido por ordem de grandeza no SQL Server (EntityFramework 4).Count() > 0

Aqui está a consulta com Anyessa exceção de tempo limite (em ~ 200.000 registros):

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 versão executada em questão de milissegundos:

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();

Preciso encontrar uma maneira de ver o SQL exato que ambos os LINQs produzem - mas é óbvio que há uma enorme diferença de desempenho entre Counte, Anyem alguns casos, e, infelizmente, parece que você não pode ficar Anyem todos os casos.

Edição: Aqui são gerados SQLs. Belezas como você pode ver;)

ANY:

exec sp_executesql N'SELECT TOP (1) 
[Projeto2]. [ContactId] AS [ContactId], 
[Projeto2]. [CompanyId] AS [CompanyId], 
[Projeto2]. [Nome do contato] AS [Nome do contato], 
[Projeto2]. [Nome completo] AS [Nome completo], 
[Project2]. [ContactStatusId] AS [ContactStatusId], 
[Projeto2]. [Criado] AS [Criado]
FROM (SELECIONE [Projeto2]. [ID do contato] AS [ID do contato], [Projeto2]. [ID da empresa] AS [ID da empresa], [Projeto2]. [Nome do contato] AS [Nome do contato], [Projeto2]. [Nome completo] AS [Nome completo] , [Projeto2]. [ContactStatusId] AS [ContactStatusId], [Projeto2]. [Criado] AS [Criado], row_number () OVER (ORDER BY [Projeto2]. [ContactId] ASC) AS [row_number]
    FROM (SELECIONAR 
        [Extent1]. [ContactId] AS [ContactId], 
        [Extensão1]. [CompanyId] AS [CompanyId], 
        [Extent1]. [ContactName] AS [ContactName], 
        [Extensão1]. [Nome completo] AS [Nome completo], 
        [Extent1]. [ContactStatusId] AS [ContactStatusId], 
        [Extensão1]. [Criado] AS [Criado]
        FROM [dbo]. [Contato] AS [Extensão1]
        WHERE ([Extent1]. [CompanyId] = @ p__linq__0) AND ([Extent1]. [ContactStatusId] <= 3) AND (NÃO EXISTE (SELECT 
            1 AS [C1]
            FROM [dbo]. [NewsletterLog] AS [Extent2]
            WHERE ([Extent1]. [ContactId] = [Extent2]. [ContactId]) AND (6 = [Extent2]. [NewsletterLogTypeId])
        )))
    ) AS [Projeto2]
) AS [Projeto2]
ONDE [Projeto2]. [Número da linha]> 99
ORDER BY [Projeto2]. [ContactId] ASC ', N' @ p__linq__0 int ', @ p__linq__0 = 4

COUNT:

exec sp_executesql N'SELECT TOP (1) 
[Projeto2]. [ContactId] AS [ContactId], 
[Projeto2]. [CompanyId] AS [CompanyId], 
[Projeto2]. [Nome do contato] AS [Nome do contato], 
[Projeto2]. [Nome completo] AS [Nome completo], 
[Project2]. [ContactStatusId] AS [ContactStatusId], 
[Projeto2]. [Criado] AS [Criado]
FROM (SELECIONE [Projeto2]. [ID do contato] AS [ID do contato], [Projeto2]. [ID da empresa] AS [ID da empresa], [Projeto2]. [Nome do contato] AS [Nome do contato], [Projeto2]. [Nome completo] AS [Nome completo] , [Projeto2]. [ContactStatusId] AS [ContactStatusId], [Projeto2]. [Criado] AS [Criado], row_number () OVER (ORDER BY [Projeto2]. [ContactId] ASC) AS [row_number]
    FROM (SELECIONAR 
        [Project1]. [ContactId] AS [ContactId], 
        [Projeto1]. [CompanyId] AS [CompanyId], 
        [Projeto1]. [Nome do contato] AS [Nome do contato], 
        [Projeto1]. [Nome completo] AS [Nome completo], 
        [Project1]. [ContactStatusId] AS [ContactStatusId], 
        [Projeto1]. [Criado] AS [Criado]
        FROM (SELECIONAR 
            [Extent1]. [ContactId] AS [ContactId], 
            [Extensão1]. [CompanyId] AS [CompanyId], 
            [Extent1]. [ContactName] AS [ContactName], 
            [Extensão1]. [Nome completo] AS [Nome completo], 
            [Extent1]. [ContactStatusId] AS [ContactStatusId], 
            [Extensão1]. [Criado] AS [Criado], 
            (SELECT 
                COUNT (1) AS [A1]
                FROM [dbo]. [NewsletterLog] AS [Extent2]
                WHERE ([Extent1]. [ContactId] = [Extent2]. [ContactId]) AND (6 = [Extent2]. [NewsletterLogTypeId])) AS [C1]
            FROM [dbo]. [Contato] AS [Extensão1]
        ) AS [Projeto1]
        WHERE ([Projeto1]. [CompanyId] = @ p__linq__0) AND ([Projeto1]. [ContactStatusId] <= 3) AND (0 = [Projeto1]. [C1])
    ) AS [Projeto2]
) AS [Projeto2]
ONDE [Projeto2]. [Número da linha]> 99
ORDER BY [Projeto2]. [ContactId] ASC ', N' @ p__linq__0 int ', @ p__linq__0 = 4

Parece que puro Where with EXISTS funciona muito pior do que calcular Count e depois fazer Where with Count == 0.

Deixe-me saber se vocês vêem algum erro nas minhas descobertas. O que se pode tirar disso tudo, independentemente da discussão Any vs Count, é que qualquer LINQ mais complexo fica muito melhor quando reescrito como Procedimento Armazenado;).


2
Gostaria de ver alguns planos de consulta SQL que são gerados por cada consulta linq para cada cenário.
— Pure.Krome

43
com base no SQL, tudo o que posso dizer é: ambas as consultas parecem horríveis. Eu sabia que havia uma razão para eu normalmente escrever meu próprio TSQL ...
— Marc Gravell

Qualquer pessoa precisaria examinar todas as linhas da mesma maneira que o Count faria. Que o seu exemplo dê um resultado tão horrível é um pouco estranho, na pior das hipóteses! Qualquer um deve ser um pouco mais lento que o Count. No seu caso, eu procuraria maneiras de simplificar a seleção, talvez dividindo-a em etapas ou reordenando as condições, se isso for possível. Mas seu argumento de que a regra Qualquer é melhor que o Conde não se aplica! Qualquer coisa melhor que o Conde é muito boa.
— Bent

25

Como esse é um tópico bastante popular e as respostas diferem, tive que dar uma nova olhada no problema.

Ambiente de teste: EF 6.1.3, SQL Server, 300k registros

Modelo de tabela :

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

    public string Name { get; set; }

    public string Surname { get; set; }
}

Código do teste:

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();
        }
    }
}

Resultados:

Qualquer () ~ 3ms

Count () ~ 230ms para a primeira consulta, ~ 400ms para a segunda

Observações:

Para o meu caso, a EF não gerou SQL como o @Ben mencionado em seu post.


4
Para uma comparação adequada, você deve fazer Count() > 0. : D
— Andrew

1
Andrew, Count ()> 0 não será executado de forma diferente de Count () neste teste em particular.
— CodeMonkeyForHire

11

EDIT: foi corrigido na versão EF 6.1.1. e essa resposta não é mais real

Para SQL Server e EF4-6, Count () executa cerca de duas vezes mais rápido que Any ().

Quando você executa o Table.Any (), ele gera algo como ( alerta: não machuque o cérebro tentando entendê-lo )

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]

isso requer 2 varreduras de linhas com sua condição.

Não gosto de escrever Count() > 0porque esconde minha intenção. Eu prefiro usar predicado personalizado para isso:

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

Eu notei isso também. O SQL Any () não faz nenhum sentido. Não sei por que eles não fazem: CASE WHEN (EXISTS (sql)) THEN 1 ELSE 0 END. Eu não consigo pensar em uma razão pela qual eles precisam fazer um NÃO EXISTE, a fim de devolver 0.
— scott.korin

Isto é falso. Você encontrou um plano de consulta incorreto por acaso. Isto acontece. Qualquer um é, quase sempre, mais rápido.
— usr

Eu verifiquei o sql gerado no 6.1.3, eles o corrigiram: SELECT CASE WHEN (EXISTS (SELECT 1 AS [C1] FROM [dbo]. [TestTables] AS [Extent1] WHERE [Extent1]. [Id]> 1000)) ENTÃO converter (1 como bit) ELSE converter (0 como bit) END AS [C1] FROM (SELECIONE 1 AS X) AS [SingleRowTable1]
— Ben Ben

6

Depende, qual o tamanho do conjunto de dados e quais são seus requisitos de desempenho?

Se não é nada gigantesco, use a forma mais legível, o que para mim é alguma, porque é mais curta e legível do que uma equação.


2

Sobre o método Count () , se o IEnumarable é um ICollection , não podemos iterar em todos os itens, pois podemos recuperar o campo Count de ICollection , se o IEnumerable não for um ICollection , devemos iterar todos os itens usando um tempo com um MoveNext , dê uma olhada no Código do .NET Framework:

public static int Count<TSource>(this IEnumerable<TSource> source)
{
    if (source == null) 
        throw Error.ArgumentNull("source");

    ICollection<TSource> collectionoft = source as ICollection<TSource>;
    if (collectionoft != null) 
        return collectionoft.Count;

    ICollection collection = source as ICollection;
    if (collection != null) 
        return collection.Count;

    int count = 0;
    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
        checked
        {
            while (e.MoveNext()) count++;
        }
    }
    return count;
}

Referência: Origem de Referência Enumerável


2

Você pode fazer um teste simples para descobrir isso:

var query = //make any query here
var timeCount = new Stopwatch();
timeCount.Start();
if (query.Count > 0)
{
}
timeCount.Stop();
var testCount = timeCount.Elapsed;

var timeAny = new Stopwatch();
timeAny.Start();
if (query.Any())
{
}
timeAny.Stop();
var testAny = timeAny.Elapsed;

Verifique os valores de testCount e testAny.


1
Aqui é o teste com o seu código de propriedade Count vs Any () Contagem vitórias de propriedade vs Any () com + 2x - ligação
— Stanislav Prusac

1
Para um resultado melhor, você pode fazer essas comparações 1000 vezes (ou mais). Ajuda a calcular a média dos resultados e evitar picos aleatórios.
— Roman

Quando você estiver testando como o método mencionado acima, precisará considerar muitos outros fatores, como carga no banco de dados / rede, planejar o cache no lado do banco de dados, etc. Para fazer um teste preciso, você também deve projetar um ambiente isolado e preciso
— Vahid Farahmandian

para uma melhor comparação deve ser Countsubstituído pelo método Count () vs .Any () não uma propriedade. Você precisa de tempo de iterações.
— daremachine

0

Se você estiver usando o Entity Framework e tiver uma tabela enorme com muitos registros, Any () será muito mais rápido. Lembro-me de uma vez que queria verificar se uma tabela estava vazia e tinha milhões de linhas. Foram necessários 20 a 30 segundos para que Count ()> 0 fosse concluído. Foi instantâneo com Any () .

Qualquer () pode ser um aprimoramento de desempenho, pois talvez não seja necessário iterar a coleção para obter o número de coisas. Só tem que acertar um deles. Ou, por exemplo, LINQ para entidades, o SQL gerado será SE EXISTE (...) em vez de SELECIONAR CONTAGEM ... ou até SELECIONAR * ....

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.