2013-04-24 13 views
27

Opublikowany wcześniej question dał mi do myślenia. Czy Any() i Count() działają podobnie, gdy są używane na pustej liście?C#: Dowolny() kontra Count() dla pustej listy

Zgodnie z objaśnieniami here, obie powinny wykonać te same kroki, co GetEnumerator()/MoveNext()/Dispose().

Testowałem to przy użyciu szybki program na LINQPad:

static void Main() 
{ 
    var list = new List<int>(); 

    Stopwatch stopwatch = new Stopwatch(); 
    stopwatch.Start(); 

    for (int i = 0; i < 10000; i++) 
     list.Any(); 

    stopwatch.Stop(); 
    Console.WriteLine("Time elapsed for Any() : {0}", stopwatch.Elapsed); 


    stopwatch = new Stopwatch(); 
    stopwatch.Start(); 

    for (int i = 0; i < 10000; i++) 
     list.Count(); 

    stopwatch.Stop(); 
    Console.WriteLine("Time elapsed for Count(): {0}", stopwatch.Elapsed); 
} 

i ogólny wynik wydaje się wskazywać, że Count() jest szybszy w tej sytuacji. Dlaczego?

Nie jestem pewien, czy uzyskałem właściwy wynik, byłbym wdzięczny za wszelkie poprawki, jeśli nie.


Edycja: Rozumiem, że miałoby to więcej sensu semantycznie. Pierwszy link opublikowany w pytaniu pokazuje sytuację, w której ma sens użycie bezpośrednio od Count(), ponieważ wartość zostanie wykorzystana, stąd pytanie.

+0

Oni obaj bardzo szybko, ale jeśli testowanie 'List' wystarczy użyć' własność Count', zamiast 'Count()' rozszerzenie, które nie wymaga wyliczenia. – Jodrell

+7

Co dokładnie wykazały testy? Spodziewam się, że wywoływanie któregokolwiek z nich zaledwie 10000 razy będzie tak szybkie, że nie da się tego zmierzyć. –

+3

Powód "Any" jest ogólnie lepszy, ponieważ wystarczy znaleźć jedną rzecz w wyliczeniu, ale liczba musi znaleźć wszystkie z nich. W teście lista jest pusta, więc najwyraźniej znajduje pierwszą, a znalezienie wszystkiego nie robi wielkiej różnicy. –

Odpowiedz

22

Metoda Count() jest zoptymalizowana pod kątem typu ICollection<T>, dlatego wzór GetEnumerator()/MoveNext()/Dispose() nie jest używany.

list.Count(); 

są tłumaczone na

((ICollection)list).Count; 

Podczas gdy Any() musi zbudować moduł wyliczający. Metoda Count() jest szybsza.

Tutaj test porównawczy dla 4 różnych instancji IEnumerable. MyEmpty wygląda IEnumerable<T> MyEmpty<T>() { yield break; }

iterations : 100000000 

Function      Any()  Count() 
new List<int>()    4.310  2.252 
Enumerable.Empty<int>()  3.623  6.975 
new int[0]     3.960  7.036 
MyEmpty<int>()    5.631  7.194 

Jak casperOne powiedział w komentarzu, Enumerable.Empty<int>() is ICollection<int>, ponieważ jest tablicą, a tablice nie są dobre z rozszerzeniem Count() ponieważ the cast to ICollection<int> is not trivial.

Zresztą dla domowej pustym IEnumerable, możemy zobaczyć, czego oczekiwaliśmy, że Count() jest wolniejszy niż Any(), ze względu na narzut testowania, jeśli IEnumerable jest ICollection.

Kompletna odniesienia:

class Program 
{ 
    public const long Iterations = (long)1e8; 

    static void Main() 
    { 
     var results = new Dictionary<string, Tuple<TimeSpan, TimeSpan>>(); 
     results.Add("new List<int>()", Benchmark(new List<int>(), Iterations)); 
     results.Add("Enumerable.Empty<int>()", Benchmark(Enumerable.Empty<int>(), Iterations)); 
     results.Add("new int[0]", Benchmark(new int[0], Iterations)); 
     results.Add("MyEmpty<int>()", Benchmark(MyEmpty<int>(), Iterations)); 

     Console.WriteLine("Function".PadRight(30) + "Any()".PadRight(10) + "Count()"); 
     foreach (var result in results) 
     { 
      Console.WriteLine("{0}{1}{2}", result.Key.PadRight(30), Math.Round(result.Value.Item1.TotalSeconds, 3).ToString().PadRight(10), Math.Round(result.Value.Item2.TotalSeconds, 3)); 
     } 
     Console.ReadLine(); 
    } 

    public static Tuple<TimeSpan, TimeSpan> Benchmark(IEnumerable<int> source, long iterations) 
    { 
     var anyWatch = new Stopwatch(); 
     anyWatch.Start(); 
     for (long i = 0; i < iterations; i++) source.Any(); 
     anyWatch.Stop(); 

     var countWatch = new Stopwatch(); 
     countWatch.Start(); 
     for (long i = 0; i < iterations; i++) source.Count(); 
     countWatch.Stop(); 

     return new Tuple<TimeSpan, TimeSpan>(anyWatch.Elapsed, countWatch.Elapsed); 
    } 

    public static IEnumerable<T> MyEmpty<T>() { yield break; } 
} 
+0

Użycie 'Enumerable.Empty ()' pokazuje, że 'Any()' jest szybsze niż 'Count()'. Dzięki! –

+5

-1: Twój test jest nieprawidłowy. 'Enumerable.Empty ()' zwraca pustą tablicę, a tablice implementują 'IList ' co rozszerza 'ICollection '. Potrzebujesz metody, która nie robi nic poza 'breakiem wydajności '. Połączenie w zasadzie wykrywa tę samą ścieżkę kodu w teście. – casperOne

Powiązane problemy