2011-12-29 13 views
60

robię kilka testów wydajności i zauważył, że wyrażenie LINQ jakDlaczego LINQ .Where (predicate) .First() jest szybszy niż .First (predykat)?

result = list.First(f => f.Id == i).Property 

jest wolniejszy niż

result = list.Where(f => f.Id == i).First().Property 

Wydaje licznik intuicyjne. Pomyślałbym, że pierwsze wyrażenie byłoby szybsze, ponieważ może zatrzymać zatrzymywanie iteracji po liście, gdy tylko predykat zostanie spełniony, podczas gdy ja bym pomyślał, że wyrażenie .Where() może iterować na całej liście przed wywołaniem .First() na wynikowym podzbiorze. Nawet jeśli ten ostatni ma zwarcie, nie powinien być szybszy niż pierwszy bezpośrednio, ale tak jest.

Poniżej przedstawiono dwa bardzo proste testy jednostkowe, które ilustrują to. Gdy kompilacja z optymalizacją na TestWhereAndFirst jest około 30% szybsza niż TestFirstOnly na .Net i Silverlight 4. Próbowałem, aby predykat zwrócił więcej wyników, ale różnica w wydajności jest taka sama.

Czy ktoś może wyjaśnić, dlaczego .First(fn) jest wolniejszy niż .Where(fn).First()? Widzę podobny wynik intuicyjny z .Count(fn) w porównaniu do .Where(fn).Count().

private const int Range = 50000; 

private class Simple 
{ 
    public int Id { get; set; } 
    public int Value { get; set; } 
} 

[TestMethod()] 
public void TestFirstOnly() 
{ 
    List<Simple> list = new List<Simple>(Range); 
    for (int i = Range - 1; i >= 0; --i) 
    { 
     list.Add(new Simple { Id = i, Value = 10 }); 
    } 

    int result = 0; 
    for (int i = 0; i < Range; ++i) 
    { 
     result += list.First(f => f.Id == i).Value; 
    } 

    Assert.IsTrue(result > 0); 
} 

[TestMethod()] 
public void TestWhereAndFirst() 
{ 
    List<Simple> list = new List<Simple>(Range); 
    for (int i = Range - 1; i >= 0; --i) 
    { 
     list.Add(new Simple { Id = i, Value = 10 }); 
    } 

    int result = 0; 
    for (int i = 0; i < Range; ++i) 
    { 
     result += list.Where(f => f.Id == i).First().Value; 
    } 

    Assert.IsTrue(result > 0); 
} 
+4

Jak się mierzysz? –

+3

Twoja początkowa myśl jest jednak błędna: LINQ wykonuje leniwe obliczenia, więc gdy zostanie wywołane 'First()', zapyta (zwracana wartość) 'Where (...)' dla tylko jednego dopasowania i nigdy nie prosi o inny. Tak więc dokładnie ta sama liczba elementów zostanie zbadana, gdy nazwiesz 'First (...)' (tj. Bezpośrednio z predykatem). – Jon

+1

Otrzymuję ten sam wynik, '.Where(). Pierwsze()' wynosi 0,021 sekundy, a '.First()' wynosi 0,037 sekundy. Jest to z prostą listą 'int's. – Ryan

Odpowiedz

43

Mam takie same wyniki: gdzie + pierwszy był szybszy niż pierwszy.

Jak zauważył Jon, Linq stosuje leniwą ocenę, więc wydajność powinna być (i jest) zasadniczo podobna do obu metod.

Patrząc w Reflector, najpierw używa prostej pętli foreach do iteracji w kolekcji, ale gdzie jest wiele iteratorów specjalizujących się w różnych typach kolekcji (tablice, listy itp.). Prawdopodobnie jest to, co daje Gdzie niewielka przewaga.

+0

Jestem świadomy leniwego oceniania. Pomyślałbym, że z doskonałą leniwą oceną Where + First dałby dokładnie taką samą wydajność jak First. Twoje dochodzenie z reflektorem jest bardzo przydatne, dziękuję. To z pewnością wyjaśnia, dlaczego pierwszy sam jest wolniejszy. Zastanawiam się, dlaczego Microsoft zdecydował się tylko na użycie Gdzie użyć specjalistycznych iteratorów? – dazza

+0

@ user1120411: Tak, ale najpierw (z predykatem) i gdzie chcesz zrobić to samo na różne sposoby, więc wydajność różni się. – arx

+7

Ale gdybym był programistą frameworku i po prostu zaimplementowałem First (fn) wewnętrznie jako return Where (fn) .First() będzie działać dokładnie tak samo jak aktualna implementacja First, ale szybciej! Wygląda na to, że Microsoft ma złe przełożenie. – dazza

Powiązane problemy