2012-06-14 13 views
23

Poniżej przedstawiono trzy różne implementacje odnajdywania sumy IEnumerable < int> source wraz z czasem potrzebnym, gdy źródło ma 10 000 liczb całkowitych.Porównanie agregacji z wydajnością sumy w LINQ

source.Aggregate(0, (result, element) => result + element); 

trwa 3 ms

source.Sum(c => c); 

trwa 12 ms

source.Sum(); 

trwa 1 ms

Zastanawiam się dlaczego realizacja drugiego jest cztery razy droższe niż pierwsza . Nie powinno to być takie samo jak trzecie wdrożenie.

+0

Jakie są twoje warunki testu? –

+5

jak się masz te czasy? Ile czasu wypróbowałeś wyniki? –

+0

Profilowałem go za pomocą dotTrace. Uruchomiłem go raz, ale trzy przebiegi są niezależne. – Gopal

Odpowiedz

68

Uwaga: Mój komputer działa .Net 4.5 RC, więc możliwe, że wpłyną na to moje wyniki.

Mierzenie czasu potrzebnego do wykonania metody tylko raz jest zazwyczaj mało przydatne. Może być łatwo zdominowany przez takie rzeczy jak kompilacja JIT, które nie są prawdziwymi wąskimi gardłami w prawdziwym kodzie. Z tego powodu zmierzyłem wykonanie każdej metody 100 × (w trybie Release bez dołączonego debuggera). Moje wyniki to:

  • Aggregate(): 9 ms
  • Sum(lambda): 12 ms
  • Sum(): 6 ms

Fakt Sum() jest najszybszym nie dziwi: zawiera prostą pętlę bez wywoływania delegatów, co jest naprawdę szybkie. Różnica między wartościami Sum(lambda) i Aggregate() nie jest tak znacząca, jak mierzono, ale wciąż jest. Jaki mógł być tego powód? Spójrzmy na kod decompiled dla dwóch metod:

public static TAccumulate Aggregate<TSource, TAccumulate>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func) 
{ 
    if (source == null) 
     throw Error.ArgumentNull("source"); 
    if (func == null) 
     throw Error.ArgumentNull("func"); 

    TAccumulate local = seed; 
    foreach (TSource local2 in source) 
     local = func(local, local2); 
    return local; 
} 

public static int Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector) 
{ 
    return source.Select<TSource, int>(selector).Sum(); 
} 

Jak widać, Aggregate() wykorzystuje pętlę ale Sum(lambda) wykorzystuje Select(), który z kolei używa iterator. I używanie iteratora oznacza pewne obciążenie: tworzenie obiektu iteratora i (prawdopodobnie ważniejsze) jeszcze jedno wywołanie metody dla każdego elementu.

Chodźmy sprawdzić, używając Select() jest rzeczywiście powodem pisząc własny Sum(lambda) dwa razy, raz za pomocą Select(), który powinien zachowywać się tak samo jak Sum(lambda) z ram, a raz bez użycia Select():

public static int SlowSum<T>(this IEnumerable<T> source, Func<T, int> selector) 
{ 
    return source.Select(selector).Sum(); 
} 

public static int FastSum<T>(this IEnumerable<T> source, Func<T, int> selector) 
{ 
    if (source == null) 
     throw new ArgumentNullException("source"); 
    if (selector == null) 
     throw new ArgumentNullException("selector"); 

    int num = 0; 
    foreach (T item in source) 
     num += selector(item); 
    return num; 
} 

Moje pomiary potwierdzają to, co pomyślałem:

  • SlowSum(lambda): 12 ms
  • FastSum(lambda): 9 ms
+0

Bardzo wnikliwe. Dziękuję za szczegółową odpowiedź. – Gopal

+0

Jest to jedna z najlepszych odpowiedzi, jakie widziałem na SO, doskonale wyjaśnione i dobrze wykonane. (+1 oczywiście) – RichK

Powiązane problemy