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?

Pure.Krome
fonte
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

Marc Gravell
fonte
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;).

nikib3ro
fonte
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.

kamil-mrzyglod
fonte
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;
    }
}
Ben
fonte
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.

Timothy Gonzalez
fonte
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

Thiago Coelho
fonte
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.

Bronks
fonte
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 * ....

Janmejay Kumar
fonte