Welche Methode bietet eine bessere Leistung: .Any () vs .Count ()> 0?

578

Im System.LinqNamespace können wir jetzt unsere IEnumerables um die Erweiterungsmethoden Any () und Count () erweitern .

Mir wurde kürzlich gesagt, dass ich, wenn ich überprüfen möchte, ob eine Sammlung 1 oder mehr Elemente enthält, die .Any()Erweiterungsmethode anstelle der .Count() > 0Erweiterungsmethode verwenden sollte, da die .Count()Erweiterungsmethode alle Elemente durchlaufen muss.

Zweitens haben einige Sammlungen eine Eigenschaft (keine Erweiterungsmethode), die Countoder ist Length. Wäre es besser, diese anstelle von .Any()oder zu verwenden .Count()?

ja / nae?

Pure.Krome
quelle
Verwenden Sie besser Any () für Aufzählungen und zählen Sie für Sammlungen. Wenn jemand das Gefühl hat, dass das Schreiben von '(somecollection.Count> 0)' die Lesbarkeit verwirrt oder verursacht, schreiben Sie es besser als Erweiterungsmethode mit dem Namen Any (). Dann waren alle zufrieden. In Bezug auf Leistung und Lesbarkeit. Damit Ihr gesamter Code konsistent ist und sich die einzelnen Entwickler in Ihrem Projekt nicht um Count vs Any kümmern müssen.
Mahesh Bongani

Antworten:

709

Wenn Sie mit etwas beginnen , das eine hat .Lengthoder .Count(wie ICollection<T>, IList<T>, List<T>usw.) - dann wird dies die schnellste Option sein, da sie nicht durch das gehen muss GetEnumerator()/ MoveNext()/ Dispose()Sequenz erforderlich Any()für eine nicht-leer überprüfen IEnumerable<T>Sequenz .

Denn nur IEnumerable<T>dann Any()wird es in der Regel schneller gehen, da nur eine Iteration betrachtet werden muss. Beachten Sie jedoch, dass die LINQ-to-Objects-Implementierung von Count()prüft ICollection<T>( .Countals Optimierung verwendet). Wenn Ihre zugrunde liegende Datenquelle also direkt eine Liste / Sammlung ist, gibt es keinen großen Unterschied. Fragen Sie mich nicht, warum das nicht generische nicht verwendet wird ICollection...

Wenn Sie LINQ zum Filtern usw. ( Whereusw.) verwendet haben, haben Sie natürlich eine iteratorblockbasierte Sequenz, sodass diese ICollection<T>Optimierung unbrauchbar ist.

Im Allgemeinen mit IEnumerable<T>: Any()bleib bei ;-p

Marc Gravell
quelle
9
Marc: ICollection <T> leitet sich eigentlich nicht von ICollection ab. Ich war auch überrascht, aber Reflector lügt nicht.
Bryan Watts
7
Überprüft keine () Implementierung die ICollection-Schnittstelle und danach die Count-Eigenschaft?
Derigel
313
Ich denke, es gibt einen anderen Grund, Any () die meiste Zeit zu verwenden. Es signalisiert die genaue Absicht des Entwicklers. Wenn Sie nicht daran interessiert sind, die Anzahl der Elemente zu kennen, sondern nur, wenn es einige gibt, dann ist somecollection.Any () einfacher und klarer als somecollection.Count> 0
TJKjaer
13
@huttelihut - Wie viele Entwickler kennen Sie, die von der Aussage wirklich verwirrt sind (somecollection.Count > 0)? War unser gesamter Code vor der Einführung der .Any () -Methode von LINQ schwer zu verstehen?
CraigTP
25
@JLRishe - Ich bin immer noch der Meinung, dass dies someCollection.Count > 0genauso klar ist wie someCollection.Any()der zusätzliche Vorteil einer höheren Leistung und der Tatsache, dass kein LINQ erforderlich ist. Zugegeben, dies ist ein sehr einfacher Fall, und andere Konstrukte, die LINQ-Operatoren verwenden, vermitteln die Absicht der Entwickler viel klarer als die entsprechende Nicht-LINQ-Option.
CraigTP
65

Hinweis: Ich habe diese Antwort geschrieben, als Entity Framework 4 aktuell war. Der Sinn dieser Antwort war nicht in trivial zu bekommen .Any()vs .Count()Performance - Tests. Es ging darum zu signalisieren, dass EF alles andere als perfekt ist. Neuere Versionen sind besser ... aber wenn Sie einen Teil des Codes haben, der langsam ist und EF verwendet, testen Sie mit direktem TSQL und vergleichen Sie die Leistung, anstatt sich auf Annahmen zu verlassen (das .Any()ist IMMER schneller als .Count() > 0).


Obwohl ich mit den meisten Antworten und Kommentaren einverstanden bin - insbesondere in Bezug auf den Punkt Any, der die Absicht des Entwicklers besser signalisiert als Count() > 0-, hatte ich eine Situation, in der Count auf SQL Server (EntityFramework 4) um eine Größenordnung schneller ist.

Hier ist eine Abfrage mit Anydieser Ausnahme des Timeouts (bei ~ 200.000 Datensätzen):

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 in Millisekunden ausgeführt:

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

Ich muss einen Weg finden, um zu sehen, welches genaue SQL beide LINQs produzieren - aber es ist offensichtlich, dass zwischen Countund Anyin einigen Fällen ein großer Leistungsunterschied besteht , und leider scheint es, dass Sie nicht Anyin allen Fällen einfach bleiben können.

EDIT: Hier werden SQLs generiert. Schönheiten wie du sehen kannst;)

ANY::

exec sp_executesql N'SELECT TOP (1) 
[Project2]. [ContactId] AS [ContactId], 
[Project2]. [CompanyId] AS [CompanyId], 
[Projekt2]. [Kontaktname] AS [Kontaktname], 
[Project2]. [FullName] AS [FullName], 
[Project2]. [ContactStatusId] AS [ContactStatusId], 
[Projekt2]. [Erstellt] AS [Erstellt]
FROM (SELECT [Project2]. [ContactId] AS [ContactId], [Project2]. [CompanyId] AS [CompanyId], [Project2]. [ContactName] AS [ContactName], [Project2]. [FullName] AS [FullName] , [Project2]. [ContactStatusId] AS [ContactStatusId], [Project2]. [Erstellt] AS [Erstellt], 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], 
        [Umfang1]. [Erstellt] AS [Erstellt]
        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]
            WO ([Extent1]. [ContactId] = [Extent2]. [ContactId]) UND (6 = [Extent2]. [NewsletterLogTypeId])
        ))
    ) AS [Projekt2]
) AS [Projekt2]
WO [Projekt2]. [Zeilennummer]> 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], 
[Projekt2]. [Kontaktname] AS [Kontaktname], 
[Project2]. [FullName] AS [FullName], 
[Project2]. [ContactStatusId] AS [ContactStatusId], 
[Projekt2]. [Erstellt] AS [Erstellt]
FROM (SELECT [Project2]. [ContactId] AS [ContactId], [Project2]. [CompanyId] AS [CompanyId], [Project2]. [ContactName] AS [ContactName], [Project2]. [FullName] AS [FullName] , [Project2]. [ContactStatusId] AS [ContactStatusId], [Project2]. [Erstellt] AS [Erstellt], row_number () OVER (ORDER BY [Project2]. [ContactId] ASC) AS [row_number]
    FROM (SELECT 
        [Projekt1]. [Kontakt-ID] AS [Kontakt-ID], 
        [Projekt1]. [CompanyId] AS [CompanyId], 
        [Projekt1]. [Kontaktname] AS [Kontaktname], 
        [Projekt1]. [Vollständiger Name] AS [Vollständiger Name], 
        [Projekt1]. [ContactStatusId] AS [ContactStatusId], 
        [Projekt1]. [Erstellt] AS [Erstellt]
        FROM (SELECT 
            [Extent1]. [ContactId] AS [ContactId], 
            [Extent1]. [CompanyId] AS [CompanyId], 
            [Extent1]. [ContactName] AS [ContactName], 
            [Extent1]. [FullName] AS [FullName], 
            [Extent1]. [ContactStatusId] AS [ContactStatusId], 
            [Umfang1]. [Erstellt] AS [Erstellt], 
            (WÄHLEN 
                COUNT (1) AS [A1]
                FROM [dbo]. [NewsletterLog] AS [Extent2]
                WO ([Extent1]. [ContactId] = [Extent2]. [ContactId]) UND (6 = [Extent2]. [NewsletterLogTypeId])) AS [C1]
            FROM [dbo]. [Contact] AS [Extent1]
        ) AS [Projekt1]
        WHERE ([Project1]. [CompanyId] = @ p__linq__0) AND ([Project1]. [ContactStatusId] <= 3) AND (0 = [Project1]. [C1])
    ) AS [Projekt2]
) AS [Projekt2]
WO [Projekt2]. [Zeilennummer]> 99
ORDER BY [Project2]. [ContactId] ASC ', N' @ p__linq__0 int ', @ p__linq__0 = 4

Scheint, dass reines Where with EXISTS viel schlechter funktioniert als das Berechnen von Count und dann Where with Count == 0.

Lassen Sie mich wissen, wenn Sie einen Fehler in meinen Ergebnissen sehen. Was aus all dem herausgenommen werden kann, ist, dass komplexere LINQ viel besser dran sind, wenn sie als gespeicherte Prozedur umgeschrieben werden;).

nikib3ro
quelle
2
Ich würde gerne einige SQL-Abfragepläne sehen, die von jeder Linq-Abfrage für jedes Szenario generiert werden.
Pure.Krome
43
Basierend auf SQL kann ich nur sagen: Beide Abfragen sehen schrecklich aus. Ich wusste, dass es einen Grund gibt, warum ich normalerweise meine eigene TSQL schreibe ...
Marc Gravell
Jeder müsste alle Zeilen genau wie Count durchsehen. Dass Ihr Beispiel solch ein schreckliches Ergebnis liefert, ist im schlimmsten Fall etwas seltsam! Jedes sollte nur ein bisschen langsamer sein als Count. In Ihrem Fall würde ich nach Möglichkeiten suchen, die Auswahl zu vereinfachen, sie möglicherweise schrittweise aufzuteilen oder die Bedingungen neu zu ordnen, wenn dies möglich ist. Aber Ihr Punkt, dass die Any-Regel besser ist als Count, gilt nicht! Any ist besser als Count, ist sehr gut.
Gebogen
25

Da dies ein ziemlich beliebtes Thema ist und die Antworten unterschiedlich sind, musste ich das Problem neu betrachten.

Testen von env: EF 6.1.3, SQL Server, 300.000 Datensätze

Tabellenmodell :

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

    public string Name { get; set; }

    public string Surname { get; set; }
}

Testcode:

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

Ergebnisse:

Beliebig () ~ 3ms

Count () ~ 230 ms für die erste Abfrage, ~ 400 ms für die zweite

Bemerkungen:

In meinem Fall hat EF kein SQL generiert, wie in seinem Beitrag @Ben erwähnt.

kamil-mrzyglod
quelle
4
Für einen richtigen Vergleich sollten Sie dies tun Count() > 0. : D
Andrew
1
Andrew, Count ()> 0 wird in diesem speziellen Test nicht anders ausgeführt als Count ().
CodeMonkeyForHire
11

BEARBEITEN: Es wurde in EF Version 6.1.1 behoben. und diese Antwort ist nicht mehr aktuell

Bei SQL Server und EF4-6 ist Count () etwa doppelt so schnell wie Any ().

Wenn Sie Table.Any () ausführen, wird so etwas wie ( Warnung: Verletzen Sie nicht das Gehirn, das versucht, es zu verstehen ) generiert.

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]

Das erfordert 2 Scans von Zeilen mit Ihrem Zustand.

Ich schreibe nicht gern, Count() > 0weil es meine Absicht verbirgt. Ich bevorzuge es, ein benutzerdefiniertes Prädikat dafür zu verwenden:

public static class QueryExtensions
{
    public static bool Exists<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
    {
        return source.Count(predicate) > 0;
    }
}
Ben
quelle
Das ist mir auch aufgefallen. Any () SQL macht überhaupt keinen Sinn. Ich bin mir nicht sicher, warum sie das nicht tun: CASE WHEN (EXISTS (sql)) THEN 1 ELSE 0 END. Ich kann mir keinen Grund vorstellen, warum sie ein NOT EXISTS machen müssen, um 0 zurückzugeben.
scott.korin
Das ist falsch. Sie haben zufällig einen schlechten Abfrageplan gefunden. Das passiert. Jeder ist fast immer schneller.
usr
Ich habe die in 6.1.3 generierte SQL überprüft und behoben: SELECT CASE WHEN (EXISTS (SELECT 1 AS [C1] FROM [dbo]. [TestTables] AS [Extent1] WHERE [Extent1]. [Id]> 1000)) DANN Cast (1 als Bit) ELSE Cast (0 als Bit) END AS [C1] FROM (SELECT 1 AS X) AS [SingleRowTable1]
Ben
6

Es kommt darauf an, wie groß der Datensatz ist und welche Leistungsanforderungen Sie haben.

Wenn es nichts Gigantisches ist, verwenden Sie die am besten lesbare Form, die für mich eine ist, weil sie kürzer und lesbarer ist als eine Gleichung.

Timothy Gonzalez
quelle
2

Über die Count () -Methode: Wenn die IEnumarable eine ICollection ist , können wir nicht über alle Elemente iterieren, da wir das Count- Feld der ICollection abrufen können. Wenn die IEnumerable keine ICollection ist , müssen wir mit einer Weile mit über alle Elemente iterieren Schauen Sie sich in einem MoveNext den .NET Framework-Code an:

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

Referenz: Referenzquelle aufzählbar

Thiago Coelho
quelle
2

Sie können einen einfachen Test durchführen, um dies herauszufinden:

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;

Überprüfen Sie die Werte von testCount und testAny.

Bronks
quelle
1
Hier ist ein Test mit Ihrem Code für Count-Eigenschaft gegen Any () Count-Eigenschaft gewinnt gegen Any () mit + 2x - Link
Stanislav Prusac
1
Für ein besseres Ergebnis können Sie diese Vergleiche 1000 Mal (oder öfter) durchführen. Es hilft, die Ergebnisse zu mitteln und zufällige Spitzen zu vermeiden.
Roman
Wenn Sie wie die oben genannte Methode testen, müssen Sie viele weitere Faktoren berücksichtigen, z. B. die Belastung Ihrer Datenbank / Ihres Netzwerks, das Planen des Caching auf der Datenbankseite usw. Um einen genauen Test durchzuführen, sollten Sie auch eine isolierte und genaue Umgebung entwerfen
Vahid Farahmandian
Zum besseren Vergleich sollte Countdurch die Methode Count () vs .Any () keine Eigenschaft ersetzt werden. Sie benötigen Zeit für Iterationen.
Daremachine
0

Wenn Sie das Entity Framework verwenden und eine große Tabelle mit vielen Datensätzen haben, ist Any () viel schneller. Ich erinnere mich, dass ich einmal überprüfen wollte, ob eine Tabelle leer war und Millionen von Zeilen hatte. Es dauerte 20 bis 30 Sekunden, bis Count ()> 0 abgeschlossen war. Es war sofort mit Any () .

Any () kann eine Leistungsverbesserung sein, da die Auflistung möglicherweise nicht iteriert werden muss, um die Anzahl der Dinge zu ermitteln. Es muss nur einen von ihnen treffen. Oder zum Beispiel für LINQ-to-Entities lautet das generierte SQL IF EXISTS (...) und nicht SELECT COUNT ... oder sogar SELECT * ....

Janmejay Kumar
quelle