W .NET, która pętla działa szybciej, „for” lub „foreach”?

345

W C # / VB.NET / .NET, która pętla działa szybciej forlub foreach?

Odkąd przeczytałem, że już dawnofor pętla działa szybciej niż foreachpętla , zakładałem, że jest prawdziwa dla wszystkich kolekcji, zbiorów ogólnych, wszystkich tablic itp.

Przeszukałem Google i znalazłem kilka artykułów, ale większość z nich jest niejednoznaczna (czytaj komentarze do artykułów) i otwarta.

Idealnym rozwiązaniem byłoby wyszczególnienie każdego scenariusza i najlepsze rozwiązanie tego samego.

Na przykład (tylko przykład tego, jak powinno być):

  1. do iteracji tablicy ponad 1000 ciągów - forjest lepszy niżforeach
  2. do iteracji po IList(nie rodzajowych) ciągach - foreachjest lepszy niżfor

Kilka odnośników znalezionych w sieci dla tego samego:

  1. Oryginalny wielki stary artykuł Emmanuela Schanzera
  2. CodeProject FOREACH vs. DLA
  3. Blog - To foreachlub nie foreach, to jest pytanie
  4. Forum ASP.NET - NET 1.1 C # forvsforeach

[Edytować]

Oprócz aspektu czytelności, naprawdę interesują mnie fakty i liczby. Istnieją aplikacje, w których ostatnia mila optymalizacji wydajności ma znaczenie.

Binoj Antony
źródło
3
Różnica wciąż istnieje. Zwłaszcza tablice powinny być tak samo szybkie pod foreach, ale we wszystkich innych przypadkach zwykłe pętle są szybsze. Oczywiście przez większość czasu nie robi to różnicy, a sprytny kompilator JIT teoretycznie mógłby wyeliminować różnicę.
lipiec
3
Bez kontekstu nie wiem dokładnie, co robisz, ale co się stanie, gdy znajdziesz częściowo wypełnioną tablicę?
Chris Cudmore,
6
Nawiasem mówiąc, 2 miliony odsłon / miesiąc nie jest niczym strasznym. Średnio jest to mniej niż trafienie na sekundę.
Mehrdad Afshari,
37
Ważna uwaga : to pytanie zostało wczoraj połączone z całkowicie niezwiązanym pytaniem o zmuszanie do używania foreachzamiast forw języku C #. Jeśli widzisz tutaj odpowiedzi, które w ogóle nie mają sensu, właśnie dlatego. Obwiniaj moderatora, a nie nieszczęśliwe odpowiedzi.
TED
7
@TED ​​Oh Zastanawiałem się, skąd bierze się komentarz „Twój szef jest idiotą”, dzięki
Gaspa79

Odpowiedzi:

350

Patrick Smacchia blogował o tym w zeszłym miesiącu, z następującymi wnioskami:

  • ponieważ pętle na liście są nieco ponad 2 razy tańsze niż pętle foreach na liście.
  • Pętla na tablicy jest około 2 razy tańsza niż pętla na liście.
  • W konsekwencji zapętlanie tablicy przy użyciu for jest 5 razy tańsze niż zapętlenie listy przy użyciu foreach (co moim zdaniem jest wszystkim, co robimy).
Ian Nelson
źródło
130
Nigdy jednak nie zapominaj: „Przedwczesna optymalizacja jest źródłem wszelkiego zła”.
Oorang
18
@Hardwareguy: skoro wiesz, że jest prawie niepostrzeżenie szybszy, dlaczego nie powinieneś zacząć go używać? Nie zajmuje to więcej czasu.
DevinB,
47
@devinb, użycie „for” jest trudniejsze niż użycie „foreach”, ponieważ dodaje kod, inną zmienną, warunek, który należy sprawdzić itp. Ile razy widziałeś błąd „jeden po drugim” w pętli „foreach” ?
tster,
35
@Hardwareguy, pozwól mi zobaczyć, czy mam to proste. Zapętlenie listy za pomocą zajmuje 5 razy dłużej foreachniż zapętlenie tablicy for, a ty nazywasz to nieistotnym? Tego rodzaju różnica w wydajności może mieć znaczenie dla twojej aplikacji i może nie, ale nie po prostu odrzuciłbym to z ręki.
Robert Harvey,
44
Czytając post na blogu, wygląda na to, że testy zostały uruchomione w Debugowaniu, a nie w Wersji, więc może mieć to jakiś czynnik. Dodatkowo różnica dotyczy tylko narzutu pętli. Nie wpływa to wcale na czas wykonania treści pętli, co w większości przypadków jest znacznie dłuższe niż czas potrzebny na przejście do następnego elementu listy. Dobrze jest wiedzieć, kiedy zauważysz, że wyraźnie występuje problem, i dokładnie zmierzyłeś różnicę w swojej aplikacji oraz zauważalną poprawę, ale zdecydowanie nie jest to ogólna porada, aby usunąć wszystkie foreachs.
Davy8
164

Po pierwsze, roszczenie wzajemne do odpowiedzi Dmitry'ego (obecnie usunięte) . W przypadku tablic kompilator C # emituje w dużej mierze ten sam kod, foreachjak w przypadku równoważnej forpętli. To wyjaśnia, dlaczego dla tego testu porównawczego wyniki są w zasadzie takie same:

using System;
using System.Diagnostics;
using System.Linq;

class Test
{
    const int Size = 1000000;
    const int Iterations = 10000;

    static void Main()
    {
        double[] data = new double[Size];
        Random rng = new Random();
        for (int i=0; i < data.Length; i++)
        {
            data[i] = rng.NextDouble();
        }

        double correctSum = data.Sum();

        Stopwatch sw = Stopwatch.StartNew();
        for (int i=0; i < Iterations; i++)
        {
            double sum = 0;
            for (int j=0; j < data.Length; j++)
            {
                sum += data[j];
            }
            if (Math.Abs(sum-correctSum) > 0.1)
            {
                Console.WriteLine("Summation failed");
                return;
            }
        }
        sw.Stop();
        Console.WriteLine("For loop: {0}", sw.ElapsedMilliseconds);

        sw = Stopwatch.StartNew();
        for (int i=0; i < Iterations; i++)
        {
            double sum = 0;
            foreach (double d in data)
            {
                sum += d;
            }
            if (Math.Abs(sum-correctSum) > 0.1)
            {
                Console.WriteLine("Summation failed");
                return;
            }
        }
        sw.Stop();
        Console.WriteLine("Foreach loop: {0}", sw.ElapsedMilliseconds);
    }
}

Wyniki:

For loop: 16638
Foreach loop: 16529

Następnie, sprawdzenie, czy Greg podkreśla, że ​​typ kolekcji jest ważny - zmień tablicę na a List<double>powyżej, a otrzymasz radykalnie różne wyniki. Jest nie tylko ogólnie wolniejszy, ale foreach staje się znacznie wolniejszy niż dostęp przez indeks. Powiedziawszy to, nadal prawie zawsze wolę foreach od pętli for, co upraszcza kod - ponieważ czytelność jest prawie zawsze ważna, podczas gdy mikrooptymalizacja rzadko.

Jon Skeet
źródło
„zmień tablicę na List <double> powyżej, a otrzymasz radykalnie różne wyniki” Bardzo interesujące, nie myślałem o tym
John
5
Biorąc pod uwagę dziwne różnice w wynikach między moimi testami a wynikami innych osób, myślę, że zasługuje to na wpis na blogu ...
Jon Skeet
1
Które prawie zawsze wolisz między tablicami i List<T>? Czy w tym przypadku również czytelność przebija mikrooptymalizację?
JohnB
12
@JohnB: Tak - prawie zawsze wolę List<T>tablice. Wyjątkami są char[]i byte[]które są częściej traktowane jako „fragmenty” danych niż zwykłe kolekcje.
Jon Skeet
Niesamowite, dostaję jeszcze bardziej agresywną różnicę na mojej maszynie, prawie 10% na korzyść foreach na prostych tablicach. Zgaduję, że to wszystko wynika z fluktuacji, że nie trzeba martwić się o dodatkową zmienną, powiązane kontrole itp. Byłoby bardzo interesujące, gdyby ktoś miał dogłębne wyjaśnienie tego.
Tamir Daniely,
162

foreachPętle wykazują bardziej konkretne zamiary niż forpętle .

Użycie foreachpętli pokazuje każdemu, kto używa twojego kodu, że planujesz zrobić coś z każdym członkiem kolekcji, niezależnie od jej miejsca w kolekcji. Pokazuje również, że nie modyfikujesz oryginalnej kolekcji (i zgłasza wyjątek, jeśli spróbujesz).

Inną zaletą foreachjest to, że działa na każdym IEnumerable, gdzie forma to sens IList, gdzie każdy element faktycznie ma indeks.

Jeśli jednak potrzebujesz użyć indeksu elementu, to oczywiście powinieneś mieć możliwość korzystania z forpętli. Ale jeśli nie musisz używać indeksu, posiadanie go jest po prostu zaśmiecaniem kodu.

O ile mi wiadomo, nie ma to znaczącego wpływu na wydajność. Na pewnym etapie w przyszłości może być łatwiej dostosować kod, foreachaby działał na wielu rdzeniach, ale teraz nie jest to coś, o co należy się martwić.

CTford
źródło
23
ctford: Nie, nie jest. Kompilator z pewnością nie może zmienić kolejności elementów w foreach. foreachwcale nie jest związany z programowaniem funkcjonalnym. Jest to absolutnie konieczny paradygmat programowania. Źle przypisujesz rzeczy, które wydarzyły się w TPL i PLINQ foreach.
Mehrdad Afshari,
13
@BlueTrin: Z pewnością gwarantuje zamawianie (sekcja 8.8.4 specyfikacji C # formalnie definiuje się foreachjako odpowiednik whilepętli). Myślę, że wiem, że @ctford ma na myśli. Równoległa biblioteka zadań pozwala podstawowej kolekcji na dostarczanie elementów w dowolnej kolejności (przez wywołanie .AsParallelwyliczenia). foreachnic tu nie robi, a ciało pętli jest wykonywane na jednym wątku . Jedyną równoległą rzeczą jest generowanie sekwencji.
Mehrdad Afshari,
6
Enumerable.Select ma przeciążenie, które pozwala uzyskać indeks elementu, więc nawet potrzeba indeksu nie wymaga użycia dla. Zobacz msdn.microsoft.com/en-us/library/bb534869.aspx
TrueWill
5
ForEach jest przydatny dla czytelności i oszczędzania pisania. Jednak koszty mają znaczenie; zmiana 2 lub 3 pętli for w utworzonym przeze mnie interfejsie projektanta dokumentów z (dla każdego obiektu na liście) na (dla i = 0 na list.count-1) skrócił czas odpowiedzi z 2-3 sekund na edycję do około. 5 sekund na edycję na małym dokumencie zapętlonym przez kilkaset obiektów. Teraz nawet w przypadku dużych dokumentów nie ma czasu na zapętlenie wszystkich. Nie mam pojęcia, jak to się stało. Wiem tylko, że alternatywą był skomplikowany schemat zapętlania tylko podzbioru obiektów. Przyjmę 5-minutową zmianę każdego dnia! - nie mikrooptymalizacja.
FastAl
5
@ FastAl Różnica między zwykłymi listami foreacha forwydajnością wynosi ułamki sekundy w przypadku iteracji ponad milionów elementów, więc Twój problem z pewnością nie był bezpośrednio związany z wydajnością foreach, przynajmniej nie dla kilkuset obiektów. Brzmi jak zepsuta implementacja modułu wyliczającego na dowolnej używanej liście.
Mike Marynowski
53

Za każdym razem, gdy pojawiają się spory dotyczące wydajności, wystarczy napisać mały test, aby można było wykorzystać wyniki ilościowe do wsparcia swojego przypadku.

Użyj klasy StopWatch i powtórz coś kilka milionów razy, aby uzyskać dokładność. (Może to być trudne bez pętli for):

using System.Diagnostics;
//...
Stopwatch sw = new Stopwatch()
sw.Start()
for(int i = 0; i < 1000000;i ++)
{
    //do whatever it is you need to time
}
sw.Stop();
//print out sw.ElapsedMilliseconds

Palce przekroczyły wyniki tego pokazu, że różnica jest znikoma i równie dobrze możesz po prostu zrobić wszystko, co w najbardziej konserwowalnym kodzie

Rob Fonseca-Ensor
źródło
13
Ale nie można porównywać wydajności for i foreach. Mają być używane w różnych okolicznościach.
Michael Krelin - haker
7
Zgadzam się z tobą, Michael, nie powinieneś wybierać, którego użyć na podstawie wydajności - powinieneś wybrać ten, który ma największy sens! Ale jeśli twój szef powie „Nie używaj, bo jest wolniejszy niż głoszenie”, to jest to jedyny sposób, aby przekonać go, że różnica jest znikoma
Rob Fonseca-Ensor,
„(Może to być trudne bez pętli for)” Lub możesz użyć pętli while.
jonescb
49

Zawsze będzie blisko. W przypadku tablicy czasami for jest ona nieco szybsza, ale foreachjest bardziej wyrazista i oferuje LINQ itp. Ogólnie rzecz biorąc, trzymaj się foreach.

Dodatkowo foreachmoże być zoptymalizowany w niektórych scenariuszach. Na przykład lista połączona może być okropna dla indeksatora, ale może być szybka foreach. W rzeczywistości standard LinkedList<T>nie oferuje nawet indeksatora z tego powodu.

Marc Gravell
źródło
Więc mówisz, że LinkedList<T>jest szczuplejszy niż List<T>? A jeśli zawsze będę używać foreach(zamiast for), lepiej będzie używać LinkedList<T>?
JohnB
2
@JohnB - nie szczuplejszy; po prostu inny. Na przykład każdy węzeł na połączonej liście ma dodatkowe odwołania, które nie są potrzebne w przypadku płaskiej tablicy (która również stanowi podstawę List<T>). Chodzi o to, że tańsze jest wkładanie / wyjmowanie .
Marc Gravell
36

Domyślam się, że w 99% przypadków prawdopodobnie nie będzie to znaczące, więc dlaczego miałbyś wybrać szybszy zamiast najbardziej odpowiedniego (jak najłatwiej zrozumieć / utrzymać)?

Brian Rasmussen
źródło
6
@klew, jeśli faktycznie profilujesz swój kod, nie musisz zgadywać, które 20% powinno być tak szybkie, jak to możliwe. Prawdopodobnie dowiesz się również, że faktyczna liczba pętli, które muszą być szybkie, jest znacznie niższa. Co więcej, czy naprawdę mówisz, że zapętlanie jest miejscem spędzania czasu, w przeciwieństwie do tego, co faktycznie robisz w tej pętli?
tster,
32

Jest mało prawdopodobne, aby istniała ogromna różnica wydajności między tymi dwoma. Jak zawsze w obliczu „który jest szybszy?” pytanie, zawsze powinieneś pomyśleć: „Mogę to zmierzyć”.

Napisz dwie pętle, które robią to samo w ciele pętli, wykonaj je i zrób z nich czas i zobacz, jaka jest różnica prędkości. Zrób to zarówno z prawie pustym ciałem, jak i ciałem pętli podobnym do tego, co faktycznie będziesz robić. Wypróbuj również z używanym typem kolekcji, ponieważ różne typy kolekcji mogą mieć różne cechy wydajności.

Greg Hewgill
źródło
32

Istnieją bardzo dobre powody, aby preferować foreach pętle forzamiast pętli. Jeśli możesz użyćforeach pętli, twój szef ma rację, że powinieneś.

Jednak nie każda iteracja po prostu przegląda listę w kolejności jeden po drugim. Jeśli zabrania , tak, to źle.

Gdybym był tobą, zrobiłbym wszystko, aby wszystkie naturalne pętle pętli przekształciły się w rekurencję . To by go nauczyło i jest to również dobre ćwiczenie mentalne dla ciebie.

PRZETRZĄSAĆ
źródło
Jak rekurencji porównać do forpętli i foreachpętle wydajność mądry?
JohnB
To zależy. Jeśli użyjesz rekurencji ogona, a Twój kompilator jest wystarczająco inteligentny, aby to zauważyć, może być identyczny. OTOH: Jeśli tak się nie stanie i zrobisz coś głupiego, na przykład przekażesz wiele niepotrzebnych (niezmiennych) danych jako parametrów lub zadeklarujesz duże struktury na stosie jako lokalne, może to być naprawdę bardzo wolne (lub nawet zabraknie pamięci RAM).
TED
Ahhh Rozumiem, dlaczego teraz o to pytałeś. Ta odpowiedź dotyczyła zupełnie innego pytania. Z jakiegoś dziwnego powodu Jonathan Sampson połączył je wczoraj. Naprawdę nie powinien był tego robić. Połączone odpowiedzi nie będą miały żadnego sensu.
TED
18

Jeffrey Richter na TechEd 2005:

„Przez lata uczyłem się, że kompilator C # jest w zasadzie dla mnie kłamcą”. .. „Chodzi o wiele rzeczy.” .. „Jak podczas wykonywania pętli foreach ...” .. „… to jedna mała linia kodu, którą piszesz, ale to, co kompilator C # wyrzuca, aby to zrobić, jest fenomenalne. spróbuj / w końcu zablokuj tam, wewnątrz bloku w końcu rzutuje zmienną na interfejs IDisposable, a jeśli rzutowanie się powiedzie, wywołuje na nim metodę Dispose, wewnątrz pętli wywołuje właściwość Current i metodę MoveNext wielokrotnie w pętli, obiekty są tworzone pod pokrywami. Wiele osób używa foreach, ponieważ jest to bardzo łatwe do kodowania, bardzo łatwe do zrobienia .. „..” foreach nie jest zbyt dobry pod względem wydajności,

Transmisja internetowa na żądanie: http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032292286&EventCategory=3&culture=en-US&CountryCode=US

Max Toro
źródło
12

To jest niedorzeczne. Nie ma ważnego powodu, aby zakazać pętli for, pod względem wydajności lub innej.

Zobacz blog Jona Skeeta, w którym znajduje się test wydajności i inne argumenty.

Rik
źródło
2
Zaktualizowany link: codeblog.jonskeet.uk/2009/01/29/...
Matt
Szybsza konstrukcja pętli zależy od tego, co trzeba powtórzyć. Kolejny blog, który porównuje wiele iteracji z wieloma rodzajami obiektów , takich jak DataRows i obiekty niestandardowe. Obejmuje to również wydajność konstrukcji pętli While, a nie tylko konstrukcje pętli for i foreach.
Darmowy koder 24
11

W przypadkach, gdy pracujesz z kolekcją obiektów, foreachlepiej, ale jeśli zwiększysz liczbę, afor pętla jest lepsza.

Pamiętaj, że w ostatnim przypadku możesz zrobić coś takiego:

foreach (int i in Enumerable.Range(1, 10))...

Ale z pewnością nie działa lepiej, w rzeczywistości ma gorszą wydajność w porównaniu do for.

Meta rycerz
źródło
„Lepsze” jest dyskusyjne: jest wolniejsze i debuger dnspy ​​nie włamie się do foreach C # (chociaż debugger VS2017 zrobi to). Czasami jest bardziej czytelny, ale jeśli obsługujesz języki bez niego, może to być denerwujące.
Zeek2
10

To powinno cię uratować:

public IEnumerator<int> For(int start, int end, int step) {
    int n = start;
    while (n <= end) {
        yield n;
        n += step;
    }
}

Posługiwać się:

foreach (int n in For(1, 200, 4)) {
    Console.WriteLine(n);
}

Aby uzyskać większą wygraną, możesz wziąć trzech delegatów jako parametry.

akuhn
źródło
1
Jedyną niewielką różnicą jest to, że forzwykle zapisywana jest pętla wykluczająca koniec zakresu (np 0 <= i < 10.). Parallel.Forrobi to również, aby łatwo było go zamienić na wspólną forpętlę.
Groo,
9

możesz przeczytać o tym w Deep .NET - część 1 Iteracja

obejmuje wyniki (bez pierwszej inicjalizacji) z kodu źródłowego .NET aż do dezasemblacji.

na przykład - Iteracja macierzy za pomocą pętli foreach: wprowadź opis zdjęcia tutaj

oraz - wypisz iterację z pętlą foreach: wprowadź opis zdjęcia tutaj

a wyniki końcowe: wprowadź opis zdjęcia tutaj

wprowadź opis zdjęcia tutaj

lub Jaakow
źródło
8

Różnice prędkości w pętli for- i a - foreachsą niewielkie, gdy przeglądasz typowe struktury, takie jak tablice, listy itp., I robiszLINQ wyszukiwanie w kolekcji jest prawie zawsze nieco wolniejsze, chociaż ładniej jest pisać! Jak powiedzieli inni plakaty, wybieraj wyrazistość zamiast milisekundy dodatkowej wydajności.

Do tej pory nie powiedziano, że foreachkompilacja pętli jest optymalizowana przez kompilator na podstawie kolekcji, nad którą się iteruje. Oznacza to, że jeśli nie masz pewności, której pętli użyć, powinieneś użyćforeach pętli - wygeneruje ona najlepszą pętlę po skompilowaniu. Jest też bardziej czytelny.

Inną kluczową zaletą foreachpętli jest to, że jeśli implementacja kolekcji zmieni się ( na przykład z int arrayna int List<int>), wówczas foreachpętla nie będzie wymagać żadnych zmian kodu:

foreach (int i in myCollection)

Powyższe jest takie samo, bez względu na typ kolekcji, podczas gdy w forpętli następujące elementy nie będą budowane, jeśli zmienisz myCollectionz a arrayna List:

for (int i = 0; i < myCollection.Length, i++)
Alex York
źródło
7

„Czy są jakieś argumenty, które mogłyby mi pomóc przekonać go, że można użyć pętli for?”

Nie, jeśli twój szef zarządza na tyle, że mówi, z jakiego języka programowania należy korzystać, naprawdę nie możesz nic powiedzieć. Przepraszam.

Rozpoznać
źródło
7

Prawdopodobnie zależy to od rodzaju wyliczanej kolekcji i implementacji jej modułu indeksującego. Ogólnie rzecz biorąc, używanie foreachmoże być lepszym podejściem.

Będzie także działał z dowolnymi IEnumerable- nie tylko z indeksatorami.

Andrew Kennan
źródło
7

To ma te same dwie odpowiedzi, co większość pytań „co jest szybsze”:

1) Jeśli nie mierzysz, nie wiesz.

2) (Ponieważ ...) To zależy.

Zależy to od tego, jak droga jest metoda „MoveNext ()”, w stosunku do tego, jak droga jest metoda „this [int index]”, dla typu (lub typów) IEnumerable, nad którym będziesz iterować.

Słowo kluczowe „foreach” jest skrótem dla szeregu operacji - wywołuje GetEnumerator () jeden raz w IEnumerable, wywołuje MoveNext () raz na iterację, wykonuje sprawdzanie typów i tak dalej. Najbardziej prawdopodobne, że wpływ na pomiary wydajności będzie miał koszt MoveNext (), ponieważ jest on wywoływany O (N) razy. Może to tanie, ale może nie.

Słowo kluczowe „for” wygląda bardziej przewidywalnie, ale w większości pętli „for” znajdziesz coś w rodzaju „kolekcja [indeks]”. Wygląda to na prostą operację indeksowania tablic, ale w rzeczywistości jest to wywołanie metody, której koszt zależy wyłącznie od charakteru kolekcji, nad którą się iteruje. Prawdopodobnie jest tani, ale może nie jest.

Jeśli podstawowa struktura kolekcji jest w zasadzie połączoną listą, MoveNext jest tani, ale indeksator może mieć koszt O (N), co czyni prawdziwy koszt pętli „for” O (N * N).

NSFW
źródło
6

Każdy konstrukt językowy ma odpowiedni czas i miejsce do użycia. Istnieje powód, dla którego język C # ma cztery oddzielne instrukcje iteracji - każda z nich służy do określonego celu i ma odpowiednie zastosowanie.

Polecam usiąść z szefem i racjonalnie wyjaśnić, dlaczego forpętla ma cel. Są chwile, kiedy forblok iteracji bardziej precyzyjnie opisuje algorytm niż foreachiteracja. Jeśli jest to prawdą, należy z nich skorzystać.

Chciałbym również zwrócić uwagę na twojego szefa - Wydajność nie jest i nie powinna stanowić problemu w żaden praktyczny sposób - jest to bardziej kwestia wyrażenia algorytmu w zwięzły, znaczący i łatwy do utrzymania sposób. Mikrooptymalizacje takie jak ten całkowicie pomijają sens optymalizacji wydajności, ponieważ jakakolwiek rzeczywista korzyść wydajności będzie wynikać z przeprojektowania algorytmu i refaktoryzacji, a nie restrukturyzacji pętli.

Jeśli po racjonalnej dyskusji nadal istnieje autorytarny pogląd, od Ciebie zależy, jak postępować. Osobiście nie byłbym szczęśliwy pracując w środowisku, w którym racjonalne myślenie jest odradzane, i rozważałbym przejście na inne stanowisko pod innym pracodawcą. Jednak zdecydowanie zalecam dyskusję przed denerwowaniem - może to być zwykłe nieporozumienie.

Reed Copsey
źródło
5

To, co robisz wewnątrz pętli, ma wpływ na wydajność, a nie faktyczna konstrukcja pętli (zakładając, że twój przypadek nie jest trywialny).

Martin Wickman
źródło
5

To, czy forjest szybsze, niż foreachjest naprawdę poza tym. Poważnie wątpię, że wybranie jednego z drugiego będzie miało znaczący wpływ na twoje wyniki.

Najlepszym sposobem na zoptymalizowanie aplikacji jest profilowanie rzeczywistego kodu. To wskaże metody, które odpowiadają za najwięcej pracy / czasu. Najpierw zoptymalizuj. Jeśli wydajność nadal nie jest akceptowalna, powtórz procedurę.

Zasadniczo zalecałbym unikanie mikrooptymalizacji, ponieważ rzadko przynoszą one znaczące korzyści. Jedynym wyjątkiem jest optymalizacja zidentyfikowanych gorących ścieżek (tj. Jeśli twoje profilowanie identyfikuje kilka najczęściej używanych metod, sensowna może być ich szeroka optymalizacja).

Brian Rasmussen
źródło
Gdyby jedynym rodzajem optymalizacji, który musiałem wykonać w projektach, nad którymi pracuję, byłyby mikrooptymalizacje, byłbym szczęśliwy. Niestety, nigdy tak nie jest.
Yannick Motton
2
forjest nieznacznie szybszy niż foreach. Poważnie sprzeciwiłem się temu stwierdzeniu. To całkowicie zależy od podstawowej kolekcji. Jeśli połączona klasa listy zapewnia indeksatorowi parametr całkowitoliczbowy, oczekiwałbym, że forużyje na nim pętli O (n ^ 2), podczas gdy foreachoczekuje się, że O (n).
Mehrdad Afshari,
@Merhdad: Właściwie to dobra uwaga. Właśnie myślałem o zwykłym przypadku indeksowania listy (tj. Tablicy). Przeredaguję, aby to odzwierciedlić. Dzięki.
Brian Rasmussen,
@Mehrdad Afshari: Indeksowanie kolekcji według liczb całkowitych może być znacznie wolniejsze niż liczenie nad nią. Ale tak naprawdę porównujesz użycie for i przeglądarkę indeksującą do używania foreachsamego. Myślę, że odpowiedź @Briana Rasmussena jest poprawna, że ​​oprócz użycia z kolekcją, forzawsze będzie ona nieco szybsza niż foreach. Jednak forwyszukiwanie kolekcji zawsze będzie wolniejsze niż foreachsamo w sobie.
Daniel Pryden,
@Daniel: Albo masz prostą tablicę, dla której obie wygenerują identyczny kod, albo istnieje indeksator, gdy używasz forinstrukcji. Zwykła forpętla ze zmienną sterującą liczbą całkowitą nie jest porównywalna z foreach, więc to jest koniec. Rozumiem, co oznacza @Brian i jest to poprawne, jak mówisz, ale odpowiedź może być myląca. Re: twój ostatni punkt: nie, właściwie to forkoniec List<T>jest jeszcze szybszy niż foreach.
Mehrdad Afshari,
4

Oba będą działać prawie dokładnie w ten sam sposób. Napisz kod, aby użyć obu, a następnie pokaż mu IL. Powinien pokazywać porównywalne obliczenia, co oznacza brak różnicy w wydajności.

cjk
źródło
Kompilator rozpoznaje pętle foreach używane w tablicach / ILists itp. I zmienia je na pętle.
Callum Rogers,
3
Pokaż mu wiersze niezrozumiałego dowodu , że jest OK i poproś o jego dowód, że nie jest OK.
cjk
3

for ma prostszą logikę do wdrożenia, dzięki czemu jest szybszy niż foreach.

Tarik
źródło
3

Chyba że jesteś w konkretnym procesie optymalizacji prędkości, powiedziałbym, że użycie dowolnej metody zapewni najłatwiejszy do odczytania i utrzymania kod.

Jeśli iterator jest już skonfigurowany, tak jak w przypadku jednej z klas kolekcji, foreach jest dobrą, łatwą opcją. A jeśli iterujesz zakres liczb całkowitych, to prawdopodobnie jest czystszy.

GeekyMonkey
źródło
3

W większości przypadków tak naprawdę nie ma różnicy.

Zazwyczaj zawsze musisz używać foreach, gdy nie masz wyraźnego indeksu liczbowego, i zawsze musisz go używać, gdy nie masz w rzeczywistości kolekcji iteracyjnej (np. Iteracja po dwuwymiarowej siatce tablic w górnym trójkącie) . W niektórych przypadkach masz wybór.

Można argumentować, że pętle mogą być nieco trudniejsze do utrzymania, jeśli w kodzie zaczną pojawiać się magiczne liczby. Powinieneś mieć rację, że nie możesz użyć pętli for i musisz zbudować kolekcję lub użyć lambdy do zbudowania podkolekcji zamiast tego, ponieważ pętle for zostały zablokowane.

Cade Roux
źródło
3

Całkiem dziwne wydaje się całkowite zakazanie używania czegoś w rodzaju pętli for.

Jest tu ciekawy artykuł , który opisuje wiele różnic wydajności między dwiema pętlami.

Powiedziałbym, że osobiście uważam, że foreach jest nieco bardziej czytelny dla pętli, ale powinieneś użyć najlepszego dla danego zadania i nie musisz pisać dodatkowego długiego kodu, aby uwzględnić pętlę foreach, jeśli pętla for jest bardziej odpowiednia.

colethecoder
źródło
Niezbędny cytat z artykułu, do którego linkujesz: „... jeśli planujesz napisać kod o wysokiej wydajności, który nie jest przeznaczony do kolekcji, użyj pętli. Nawet w przypadku kolekcji foreach może wyglądać na przydatny podczas korzystania, ale nie jest tak wydajny”.
NickFitz,
3

Znalazłem foreachpętlę, która iteruje List szybciej . Zobacz moje wyniki testu poniżej. W poniższym kodzie arrayiteruję osobno rozmiary 100, 10000 i 100000, używając fori foreachzapętlając do pomiaru czasu.

wprowadź opis zdjęcia tutaj

private static void MeasureTime()
    {
        var array = new int[10000];
        var list = array.ToList();
        Console.WriteLine("Array size: {0}", array.Length);

        Console.WriteLine("Array For loop ......");
        var stopWatch = Stopwatch.StartNew();
        for (int i = 0; i < array.Length; i++)
        {
            Thread.Sleep(1);
        }
        stopWatch.Stop();
        Console.WriteLine("Time take to run the for loop is {0} millisecond", stopWatch.ElapsedMilliseconds);

        Console.WriteLine(" ");
        Console.WriteLine("Array Foreach loop ......");
        var stopWatch1 = Stopwatch.StartNew();
        foreach (var item in array)
        {
            Thread.Sleep(1);
        }
        stopWatch1.Stop();
        Console.WriteLine("Time take to run the foreach loop is {0} millisecond", stopWatch1.ElapsedMilliseconds);

        Console.WriteLine(" ");
        Console.WriteLine("List For loop ......");
        var stopWatch2 = Stopwatch.StartNew();
        for (int i = 0; i < list.Count; i++)
        {
            Thread.Sleep(1);
        }
        stopWatch2.Stop();
        Console.WriteLine("Time take to run the for loop is {0} millisecond", stopWatch2.ElapsedMilliseconds);

        Console.WriteLine(" ");
        Console.WriteLine("List Foreach loop ......");
        var stopWatch3 = Stopwatch.StartNew();
        foreach (var item in list)
        {
            Thread.Sleep(1);
        }
        stopWatch3.Stop();
        Console.WriteLine("Time take to run the foreach loop is {0} millisecond", stopWatch3.ElapsedMilliseconds);
    }

AKTUALIZACJA

Po sugestii @jgauffin użyłem kodu @johnskeet i stwierdziłem, że forpętla z arrayjest szybsza niż śledzenie,

  • Pętla Foreach z tablicą.
  • Do pętli z listą.
  • Pętla Foreach z listą.

Zobacz moje wyniki testu i kod poniżej,

wprowadź opis zdjęcia tutaj

private static void MeasureNewTime()
    {
        var data = new double[Size];
        var rng = new Random();
        for (int i = 0; i < data.Length; i++)
        {
            data[i] = rng.NextDouble();
        }
        Console.WriteLine("Lenght of array: {0}", data.Length);
        Console.WriteLine("No. of iteration: {0}", Iterations);
        Console.WriteLine(" ");
        double correctSum = data.Sum();

        Stopwatch sw = Stopwatch.StartNew();
        for (int i = 0; i < Iterations; i++)
        {
            double sum = 0;
            for (int j = 0; j < data.Length; j++)
            {
                sum += data[j];
            }
            if (Math.Abs(sum - correctSum) > 0.1)
            {
                Console.WriteLine("Summation failed");
                return;
            }
        }
        sw.Stop();
        Console.WriteLine("For loop with Array: {0}", sw.ElapsedMilliseconds);

        sw = Stopwatch.StartNew();
        for (var i = 0; i < Iterations; i++)
        {
            double sum = 0;
            foreach (double d in data)
            {
                sum += d;
            }
            if (Math.Abs(sum - correctSum) > 0.1)
            {
                Console.WriteLine("Summation failed");
                return;
            }
        }
        sw.Stop();
        Console.WriteLine("Foreach loop with Array: {0}", sw.ElapsedMilliseconds);
        Console.WriteLine(" ");

        var dataList = data.ToList();
        sw = Stopwatch.StartNew();
        for (int i = 0; i < Iterations; i++)
        {
            double sum = 0;
            for (int j = 0; j < dataList.Count; j++)
            {
                sum += data[j];
            }
            if (Math.Abs(sum - correctSum) > 0.1)
            {
                Console.WriteLine("Summation failed");
                return;
            }
        }
        sw.Stop();
        Console.WriteLine("For loop with List: {0}", sw.ElapsedMilliseconds);

        sw = Stopwatch.StartNew();
        for (int i = 0; i < Iterations; i++)
        {
            double sum = 0;
            foreach (double d in dataList)
            {
                sum += d;
            }
            if (Math.Abs(sum - correctSum) > 0.1)
            {
                Console.WriteLine("Summation failed");
                return;
            }
        }
        sw.Stop();
        Console.WriteLine("Foreach loop with List: {0}", sw.ElapsedMilliseconds);
    }
Diganta Kumar
źródło
3
To bardzo słaby test. a) robisz za mało iteracji, aby uzyskać jednoznaczną odpowiedź b) Ten wątek. Sen nie będzie tak naprawdę czekał milisekundy. Użyj tej samej metody, co Jon Skeet w swojej odpowiedzi.
jgauffin
1
99,99% czasu jest definitywnie spędzone w wątku. Sen (co nie daje żadnych gwarancji na to, jak szybko wróci, z wyjątkiem tego, że przynajmniej nie wcześniej). Pętla jest bardzo szybka, a spanie jest bardzo wolne, nie używasz później do przetestowania tego pierwszego.
Ronan Thibaudau,
3

Możesz naprawdę przykręcić mu głowę i zamiast tego wybrać IQueryable .foreach zamknięcie:

myList.ForEach(c => Console.WriteLine(c.ToString());
Tad Donaghe
źródło
3
Zastąpiłbym twój wiersz kodu myList.ForEach(Console.WriteLine).
Mehrdad Afshari,
2

Nie spodziewałbym się, że ktokolwiek znajdzie „ogromną” różnicę wydajności między tymi dwoma.

Wydaje mi się, że odpowiedź zależy od tego, czy kolekcja, do której próbujesz uzyskać dostęp, ma szybszą implementację dostępu do indeksatora, czy szybszą implementację dostępu do IEnumerator. Ponieważ IEnumerator często używa indeksatora i po prostu przechowuje kopię bieżącej pozycji indeksu, oczekiwałbym, że dostęp do enumeratora będzie co najmniej tak wolny lub wolniejszy niż bezpośredni dostęp do indeksu, ale niewiele.

Oczywiście ta odpowiedź nie uwzględnia żadnych optymalizacji, które kompilator może wdrożyć.

JohannesH
źródło
Kompilator C # robi bardzo małą optymalizację, tak naprawdę pozostawia to JITterowi.
ljs
JITter to kompilator ... prawda?
JohannesH
2

Należy pamiętać, że pętla for i pętla foreach nie zawsze są równoważne. Moduł wyliczający listy wyrzuci wyjątek, jeśli lista się zmieni, ale nie zawsze otrzymasz to ostrzeżenie z normalną pętlą for. Możesz nawet uzyskać inny wyjątek, jeśli lista zmieni się w niewłaściwym czasie.

Craig Gidney
źródło
Jeśli lista zmienia się spod ciebie, nie możesz polegać na enumeratorze wspierającym pętlę foreach, aby ci to powiedzieć. Nie będzie sprawdzać ponownie po zwróceniu wartości, co spowoduje wyścig.
hoodaticus