Który fragment kodu zapewni lepszą wydajność? Poniższe segmenty kodu zostały napisane w języku C #.
1.
for(int counter=0; counter<list.Count; counter++)
{
list[counter].DoSomething();
}
2.
foreach(MyType current in list)
{
current.DoSomething();
}
c#
performance
for-loop
foreach
Kthevar
źródło
źródło
list
naprawdę macount
zamiast elementu składowegoCount
.Odpowiedzi:
Cóż, częściowo zależy to od dokładnego typu
list
. Będzie to również zależeć od dokładnego CLR, którego używasz.To, czy jest to w jakiś sposób znaczące, czy nie, będzie zależeć od tego, czy wykonujesz jakąkolwiek prawdziwą pracę w pętli. W prawie wszystkich przypadkach różnica w wydajności nie będzie znacząca, ale różnica w czytelności sprzyja
foreach
pętli.Osobiście użyłbym LINQ, aby również uniknąć „jeśli”:
EDIT: Dla tych z Was, którzy twierdzą, że iteracja ponad
List<T>
zforeach
produkuje ten sam kod jakfor
pętla, oto dowód, że tak nie jest:Produkuje IL:
Kompilator inaczej traktuje tablice , konwertując plik
foreach
pętlę w zasadzie nafor
pętlę, ale nieList<T>
. Oto równoważny kod dla tablicy:Co ciekawe, nigdzie nie mogę znaleźć tego udokumentowanego w specyfikacji C # 3 ...
źródło
List<T>
.foreach
nad tablicą jest równoważnefor
tak czy inaczej. Zawsze najpierw koduj pod kątem czytelności, a dopiero potem mikrooptymalizuj, jeśli masz dowody , że daje to wymierne korzyści w zakresie wydajności.for
Pętla zostanie skompilowany do kodu w przybliżeniu równoważne to:Gdzie jako
foreach
pętla jest kompilowana do kodu w przybliżeniu równoważnego z tym:Jak widać, wszystko zależałoby od tego, w jaki sposób moduł wyliczający jest zaimplementowany, a jak zaimplementowany jest indeksator list. Jak się okazuje, moduł wyliczający dla typów opartych na tablicach jest zwykle zapisywany mniej więcej tak:
Jak więc widać, w tym przypadku nie zrobi to dużej różnicy, jednak moduł wyliczający dla połączonej listy prawdopodobnie wyglądałby mniej więcej tak:
W .NET zauważysz, że klasa LinkedList <T> nie ma nawet indeksatora, więc nie byłbyś w stanie wykonać pętli for na połączonej liście; ale gdybyś mógł, indeksator musiałby być napisany w ten sposób:
Jak widać, wielokrotne wywoływanie tego w pętli będzie znacznie wolniejsze niż użycie modułu wyliczającego, który zapamięta, gdzie się znajduje na liście.
źródło
Łatwy test do pół-walidacji. Zrobiłem mały test, żeby zobaczyć. Oto kod:
A oto sekcja dla wszystkich:
Kiedy wymieniłem for na foreach - foreach był o 20 milisekund szybszy - konsekwentnie . Dla było 135-139 ms, podczas gdy foreach było 113-119 ms. Kilka razy zamieniałem się tam iz powrotem, upewniając się, że to nie jakiś proces właśnie się rozpoczął.
Jednak gdy usunąłem instrukcję foo i if, for było szybsze o 30 ms (foreach to 88 ms, a for było 59 ms). Obie były pustymi muszlami. Zakładam, że foreach faktycznie przekazał zmienną, gdzie jako for po prostu zwiększał zmienną. Jeśli dodałem
Następnie for stają się wolne o około 30 ms. Zakładam, że miało to związek z utworzeniem foo i pobraniem zmiennej w tablicy i przypisaniem jej do foo. Jeśli uzyskasz dostęp do intList [i], nie będziesz mieć tej kary.
Szczerze mówiąc ... Spodziewałem się, że foreach będzie nieco wolniejsze we wszystkich okolicznościach, ale nie na tyle, aby miało to znaczenie w większości zastosowań.
edit: oto nowy kod wykorzystujący sugestie Jonsa (134217728 to największe int, jakie możesz mieć przed wyrzuceniem wyjątku System.OutOfMemory):
A oto wyniki:
Generowanie danych. Obliczanie dla pętli: 2458 ms Obliczanie każdej pętli: 2005 ms
Zamiana ich miejsc, aby zobaczyć, czy zajmuje się kolejnością rzeczy, daje takie same wyniki (prawie).
źródło
Uwaga: ta odpowiedź dotyczy bardziej języka Java niż języka C #, ponieważ C # nie ma włączonego indeksatora
LinkedLists
, ale myślę, że ogólny punkt jest nadal aktualny.Jeśli
list
akurat toLinkedList
, z którym pracujesz , to wydajność kodu indeksującego ( dostęp w stylu tablicy ) jest o wiele gorsza niż przy użyciuIEnumerator
zforeach
, dla dużych list.Po uzyskaniu dostępu do elementu 10.000 w a
LinkedList
przy użyciu składni indeksatora :,list[10000]
połączona lista rozpocznie się w węźle głównym iNext
przejdzie przez -pointer dziesięć tysięcy razy, aż dotrze do właściwego obiektu. Oczywiście, jeśli zrobisz to w pętli, otrzymasz:Kiedy wywołujesz
GetEnumerator
(niejawnie używającforach
-syntax), otrzymaszIEnumerator
obiekt, który ma wskaźnik do węzła głównego. Za każdym razem, gdy dzwoniszMoveNext
, ten wskaźnik jest przenoszony do następnego węzła, na przykład:Jak widać, w przypadku
LinkedList
s metoda indeksowania tablicy staje się coraz wolniejsza, im dłuższa jest pętla (musi w kółko przechodzić przez ten sam wskaźnik głowicy). NatomiastIEnumerable
sprawiedliwy działa w ciągłym czasie.Oczywiście, jak powiedział Jon, to naprawdę zależy od typu
list
, jeślilist
nie jestLinkedList
to tablica, ale tablica, zachowanie jest zupełnie inne.źródło
LinkedList<T>
dokumentację na MSDN i ma całkiem przyzwoity interfejs API. Co najważniejsze, nie maget(int index)
metody, takiej jak Java. Mimo to wydaje mi się, że ten punkt nadal dotyczy każdej innej struktury danych podobnej do listy, która ujawnia indeksator, który jest wolniejszy niż konkretnyIEnumerator
.Jak wspominali inni ludzie, chociaż wydajność w rzeczywistości nie ma większego znaczenia, foreach zawsze będzie trochę wolniejsze z powodu użycia
IEnumerable
/IEnumerator
w pętli. Kompilator tłumaczy konstrukcję na wywołania tego interfejsu i dla każdego kroku w konstrukcji foreach wywoływana jest funkcja + właściwość.To jest równoważne rozwinięcie konstrukcji w C #. Możesz sobie wyobrazić, jak wpływ na wydajność może się różnić w zależności od implementacji MoveNext i Current. Podczas gdy w dostępie do tablicy nie masz takich zależności.
źródło
List<T>
tutaj, nadal istnieje trafienie (prawdopodobnie wbudowane) w wywołanie indeksatora. To nie jest tak, że jest to dostęp do gołej metalowej tablicy.Po przeczytaniu wystarczającej liczby argumentów, że „pętla foreach powinna być preferowana ze względu na czytelność”, mogę powiedzieć, że moją pierwszą reakcją było „co”? Czytelność jest ogólnie rzecz biorąc subiektywna, aw tym konkretnym przypadku nawet bardziej. Dla kogoś, kto ma doświadczenie w programowaniu (praktycznie każdy język przed Javą), pętle for są znacznie łatwiejsze do odczytania niż pętle foreach. Ponadto ci sami ludzie, którzy twierdzą, że pętle foreach są bardziej czytelne, są też zwolennikami linq i innych „funkcji”, które utrudniają odczyt i konserwację kodu, co potwierdza powyższy punkt.
O wpływie na wydajność zobacz odpowiedź na to pytanie.
EDYCJA: istnieją kolekcje w C # (takie jak HashSet), które nie mają indeksatora. W tych zbiorów, foreach jest jedyną drogą do iteracji i jest to jedyny przypadek, myślę, że powinno być stosowane na na .
źródło
Jest jeszcze jeden interesujący fakt, który można łatwo przeoczyć podczas testowania szybkości obu pętli: użycie trybu debugowania nie pozwala kompilatorowi na optymalizację kodu przy użyciu ustawień domyślnych.
To doprowadziło mnie do interesującego wyniku, że foreach jest szybszy niż w trybie debugowania. Podczas gdy w trybie zwalniania for jest szybszy niż każdy inny. Oczywiście kompilator ma lepsze sposoby na optymalizację pętli for niż pętla foreach, która narusza kilka wywołań metod. Nawiasem mówiąc, pętla for jest tak fundamentalna, że możliwe, że jest nawet optymalizowana przez sam procesor.
źródło
W podanym przykładzie zdecydowanie lepiej jest użyć
foreach
pętli zamiastfor
pętli.Standardowa
foreach
konstrukcja może być szybsza (1,5 cykli na krok) niż prostafor-loop
(2 cykle na krok), chyba że pętla została rozwinięta (1,0 cykli na krok).Tak dla kodu codziennym, wydajność nie jest powodem do korzystania z bardziej złożonych
for
,while
lubdo-while
konstrukcje.Sprawdź ten link: http://www.codeproject.com/Articles/146797/Fast-and-Less-Fast-Loops-in-C
źródło
możesz o tym przeczytać w Deep .NET - część 1 Iteracja
obejmuje wyniki (bez pierwszej inicjalizacji) z kodu źródłowego .NET aż do dezasemblacji.
na przykład - Array Iteration with a foreach loop:
i - iteracja listy z pętlą foreach:
a efekty końcowe:
źródło