У .NET, який цикл працює швидше, "за" або "foreach"?


345

У C # / VB.NET / .NET, який цикл працює швидше, forабо foreach?

З того часу, як я давно прочитав, що forцикл працює швидше, ніж foreachцикл, давно припустив, що він відповідає всім колекціям, загальним колекціям, усім масивам тощо.

Я переглянув Google і знайшов кілька статей, але більшість з них є непереконливими (читайте коментарі до статей) і відкритим кінцем.

Ідеальним є те, щоб кожен сценарій був переліченим та найкращим рішенням для нього.

Наприклад (лише приклад того, як це має бути):

  1. для ітерації масиву з 1000+ рядків - forкраще, ніжforeach
  2. для ітерації над IList(не загальними) рядками - foreachкраще, ніжfor

Кілька посилань, знайдених в Інтернеті для того ж:

  1. Оригінальна старенька стаття Еммануеля Шанзера
  2. CodeProject FOREACH Vs. ЗА
  3. Блог - До foreachчи ні foreach, це питання
  4. Форум ASP.NET - NET 1.1 C # forvsforeach

[Редагувати]

Крім аспекту читабельності, мене дуже цікавлять факти та цифри. Є програми, де остання миля оптимізації продуктивності стиснута має значення.


3
Різниця все ще існує. Зокрема, масиви повинні бути настільки ж швидкими, як передбачення, але для всього іншого, прості петлі проходять швидше. Звичайно, в більшості випадків це не має значення, і, звичайно, розумний компілятор JIT може теоретично усунути різницю.
jalf

3
Без контексту я не можу точно знати, що ти робиш, але що станеться, коли ти натрапиш на частково заповнений масив?
Кріс Кадмор

6
До речі, 2 мільйони хітів на місяць - нічого страшного. Це в середньому менше удару за секунду.
Мехрдад Афшарі

37
Важлива примітка . Це питання вчора злилося з абсолютно незв'язаним питанням про те, що його змушують використовувати foreachзамість forC #. Якщо ви бачите тут відповіді, які взагалі не мають сенсу, саме тому. Звинувачуйте модератора, а не безрезультатних відповідей.
ТЕД

7
@TED ​​О, мені було цікаво, звідки всі коментарі "ваш начальник - ідіот", дякую, дякую
Gaspa79

Відповіді:


350

Про це минулого місяця Патрік Смакіа провів блог, зробивши наступні висновки:

  • петлі в списку трохи більше в 2 рази дешевші, ніж петлі foreach у списку.
  • Цикл на масиві приблизно в 2 рази дешевший, ніж циклічний цикл у списку.
  • Як наслідок, циклічне використання масиву для використання в 5 разів дешевше, ніж циклічне використання в списку за допомогою foreach (що, я вважаю, це все, що ми робимо).

130
Однак ніколи не забувайте: «Передчасна оптимізація - корінь усього зла».
Oorang

18
@Hardwareguy: Після того, як ви знаєте, що це майже непомітно швидше, чому б вам не почати його використовувати взагалі? Це не зайвий час.
DevinB

47
@devinb, використовувати "для" важче, ніж використовувати "foreach", оскільки він додає код, іншу змінну, умову, яку потрібно перевірити і т. д. Скільки разів ви бачили помилку "за одним" в циклі "foreach" ?
tster

35
@Hardwareguy, дозвольте мені побачити, чи я це зрозумів. Щоб проглянути список за допомогою потрібно 5 разів довше, foreachніж це, щоб пройти через масив for, і ви називаєте це незначним? Така різниця в продуктивності може мати значення для вашої заявки, а може і не бути, але я б не просто відпускав її з рук.
Роберт Харві

44
Читаючи публікацію в блозі, схоже, тести були запущені у налагодженні, а не в Release, так що це може мати чинник. Крім того, різниця спеціально стосується лише петлі над головою. Це зовсім не впливає на час виконання тіла циклу, що в більшості випадків набагато довше, ніж час, необхідний для переходу до наступного елемента списку. Це гарна інформація, щоб знати, коли ви виявили, що явно є проблема, і ви конкретно виміряли різницю у вашому додатку, і помітне поліпшення, але, безумовно, не загальна порада видалити всі пристрої foreach.
Davy8

164

По-перше, зустрічна претензія на відповідь Дмитра (тепер видалена) . Для масивів компілятор C # випускає в основному той самий код foreach, що і для еквівалентного forциклу. Це пояснює, чому для цього еталону результати в основному однакові:

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);
    }
}

Результати:

For loop: 16638
Foreach loop: 16529

Далі, перевірка того, що точка Грега щодо типу колекції є важливою, - змініть масив на a, List<double>зазначений вище, і ви отримаєте кардинально інші результати. Це не тільки значно повільніше загалом, але й передбачення стає значно повільнішим, ніж доступ за індексом. Сказавши це, я б майже завжди вважав за краще передбачати цикл for, де він робить простішим код - адже читабельність майже завжди важлива, тоді як мікрооптимізація рідко є.


"змінити масив на Список <подвійний> у вищесказаному, і ти отримаєш докорінно різні результати" Дуже цікаво, я про це не замислювався
johnc

5
Враховуючи дивні відмінності в результатах моїх тестів та інших людей, я думаю, що це заслуговує на публікацію в блозі ...
Джон Скіт,

1
Що ви майже завжди віддаєте перевагу між масивами та List<T>? Чи читає читання мікрооптимізація і в цьому випадку?
JohnB

12
@JohnB: Так - я майже завжди віддаю перевагу List<T>масивам. Винятки становлять char[]і byte[]які частіше трактуються як "шматки" даних, а не звичайні колекції.
Джон Скіт

Дивовижно, я набираю ще агресивнішу різницю на своїй машині, майже на 10% на користь передбачення на простих масивах. Я дико здогадуюсь, що все це випливає з тремтіння не потрібно турбуватися про зайву змінну, обмежені чеки тощо. Було б дуже цікаво, якщо хтось має це глибоке пояснення.
Тамір Даніелі

162

foreachпетлі демонструють більш конкретний намір, ніж forпетлі .

Використання foreachциклу демонструє всім, хто використовує ваш код, що ви плануєте щось зробити для кожного учасника колекції незалежно від місця в колекції. Це також показує, що ви не змінюєте оригінальну колекцію (і викидає виняток, якщо намагаєтесь).

Інша перевага foreachполягає в тому, що він працює на будь-якому IEnumerable, де як forтільки має сенс IList, де кожен елемент насправді має індекс.

Однак якщо вам потрібно використовувати індекс елемента, то, звичайно, вам слід дозволити використовувати forцикл. Але якщо вам не потрібно використовувати індекс, наявність одного - просто захаращувати ваш код.

Наскільки я знаю, суттєвих наслідків для продуктивності немає. На деякому етапі в майбутньому може бути простіше адаптувати код за допомогою foreachдекількох ядер, але зараз це не про що турбуватися.


23
ctford: Ні, це не так. Компілятор, безумовно, не може переупорядкувати елементи в foreach. foreachзовсім не пов'язане з функціональним програмуванням. Це абсолютно імперативна парадигма програмування. Ви неправильно приписуєте те, що відбувається в TPL та PLINQ foreach.
Мехрдад Афшарі

13
@BlueTrin: Це, безумовно, гарантує замовлення (розділ 8.8.4 специфікації C # формально визначається foreachяк еквівалент whileциклу). Я думаю, я знаю, що @ctford має на увазі. Паралельна бібліотека завдань дозволяє базовій колекції надати елементи в довільному порядку (за допомогою виклику .AsParallelпереліку). foreachтут нічого не робиться, і тіло циклу виконується на одній нитці . Єдине, що паралельно - це генерація послідовності.
Мехрдад Афшарі

6
Enumerable.Select має перевантаження, яка дозволяє отримати індекс елемента, тому навіть потреба в індексі не вимагає використання. Дивіться msdn.microsoft.com/en-us/library/bb534869.aspx
TrueWill

5
ForEach зручний для читабельності та збереження тексту. Однак витрати мають значення; змінивши 2 або 3 for-циклу в інтерфейсі конструктора документів, який я зробив, з (для кожного obj у списку) на (для i = 0 до list.count-1) скоротив час відгуку з 2-3 сек на редагування до приблизно. 5 секунд за редагування на невеликому документі, просто перебираючи кілька сотень об'єктів. Зараз навіть для величезних документів не існує збільшення часу для циклу всіх. Я поняття не маю, як це сталося. Що я знаю, це те, що альтернатива була складною схемою циклу лише підмножини об'єктів. Я візьму 5-хвилинну зміну в будь-який день! - не мікрооптимізація.
FastAl

5
@FastAl Різниця між показниками foreachта forпродуктивністю для звичайних списків - це частки секунди для повторення мільйонів предметів, тому ваша проблема, безумовно, не була безпосередньо пов’язана з роботою foreach, принаймні, не для кількох сотень об’єктів. Звучить як зламана реалізація перелічувача в будь-якому списку, який ви використовували.
Майк Мариновський

53

Кожен раз, коли є аргументи щодо продуктивності, просто потрібно написати невеликий тест, щоб ви могли використовувати кількісні результати для підтримки своєї справи.

Використовуйте клас секундомір і повторіть щось кілька мільйонів разів для точності. (Це може бути важко без циклу):

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

Пальці схрещували результати цього шоу, що різниця незначна, і ви можете також просто робити будь-які результати в найрентабельнішому коді


13
Але ти не можеш порівнювати ефективність роботи та передбачати. Вони повинні використовуватися в різних обставинах.
Майкл Крелін - хакер

7
Я погоджуюся з тобою, Майкл, ти не повинен вибирати, який використовувати на основі продуктивності - ти повинен вибрати той, який має найбільш сенс! Але якщо ваш начальник скаже: "Не використовуй, тому що це повільніше, ніж передбачати", то це єдиний спосіб переконати його, що різниця незначна
Роб Фонсека-Енсор

"(Це може бути важко без циклу for)" Або ви можете використовувати цикл "час".
jonescb

49

Це завжди буде поруч. Для масиву іноді for це трохи швидше, але foreachвиразніше і пропонує LINQ тощо. Загалом, дотримуйтесь foreach.

Крім того, foreachможе бути оптимізовано в деяких сценаріях. Наприклад, пов'язаний список може бути жахливим за індексатором, але він може бути швидким foreach. Насправді стандарт LinkedList<T>навіть не пропонує індексатор з цієї причини.


Так ви кажете LinkedList<T>, що худіше List<T>? І якщо я завжди буду користуватися foreach(замість for), мені краще використовувати LinkedList<T>?
JohnB

2
@JohnB - не худіший; просто різні. Наприклад, кожен вузол зв'язаного списку має додаткові посилання, які не потрібні для плоского масиву (який також є вкладеними List<T>). Більше того, що дешевше вставити / вийняти .
Марк Гравелл

36

Я здогадуюсь, що це, мабуть, не буде суттєвим у 99% випадків, то чому б ви обирали швидше, а не найбільш підходяще (як у найлегшому для розуміння / підтримання)?


6
@klew, Якщо ви справді профіліруєте свій код, вам не доведеться здогадуватися, які 20% потрібно зробити якомога швидшими. Ви, ймовірно, також дізнаєтесь, що фактична кількість циклів, які потрібно пройти швидко, значно нижча. Крім того, ви справді говорите, що акт циклічного циклу - це те, де ви проводите свій час, на відміну від того, що ви насправді робите в цьому циклі?
tster

32

Навряд чи існує величезна різниця в продуктивності між ними. Як завжди, стикаючись із "що швидше?" питання, ви завжди повинні думати "я можу це виміряти".

Напишіть дві петлі, які роблять те ж саме в тілі петлі, виконайте їх і оберіть їх час, і подивіться, яка різниця у швидкості. Зробіть це як з майже порожнім тілом, так і з тілом циклу, подібним до того, що ви насправді робите. Спробуйте також із типом колекції, який ви використовуєте, оскільки різні типи колекцій можуть мати різні характеристики продуктивності.


32

Є дуже вагомі причини віддати перевагу foreach петлям над forпетлями. Якщо ви можете використовувати foreachцикл, ваш начальник має рацію, що вам слід.

Однак не кожна ітерація просто проходить список за порядком. Якщо він забороняє , так, це неправильно.

Якби я був ти, я б зробив це, щоб перетворити все твоє природне для петель в рекурсію . Це навчило б його, і це також хороша розумова вправа для вас.


Як рекурсія порівнюється з forциклічністю та foreachциклічними показниками?
JohnB

Це залежить. Якщо ви використовуєте хвостову рекурсію і ваш компілятор досить розумний, щоб помітити, вона може бути ідентичною. OTOH: Якщо це не так, і ви робите щось дурне, як, наприклад, передавати безліч несекзанізованих (незмінних) даних у якості параметрів або оголошувати великі структури на стеку як місцеві жителі, це може бути дійсно дуже повільно (або навіть не вистачає оперативної пам’яті).
ТЕД

А-а-а. Я бачу, чому ти зараз це запитав. Ця відповідь пішла на зовсім інше питання. Чомусь вчора Джонатан Сампсон з’єднав їх двох. Він справді не повинен був цього робити. Злиті відповіді не матимуть жодного сенсу тут.
ТЕД

18

Джеффрі Ріхтер на TechEd 2005:

"Я прийшов вчитися протягом багатьох років, компілятор C # - це в основному для мене брехня". .. "Це брехня про багато речей". .. "Як, коли ви робите цикл foreach ..." .. "... це один невеликий рядок коду, який ви пишете, але те, що компілятор C # виплющує для цього, це феноменально. спробуйте / нарешті заблокуйте там, всередині остаточного блоку він перекидає вашу змінну на інтерфейс, який може бути встановлений для ідентифікації, і якщо виклик досягає, він викликає на ньому метод Dispose, всередині циклу він повторно викликає властивість Current та метод MoveNext повторно всередині циклу, об'єкти створюються під обкладинками. Багато людей використовують передбачення, тому що це дуже просто кодування, дуже легко зробити .. ".." передбачення не дуже добре з точки зору продуктивності,

Веб-трансляція за запитом: http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032292286&EventCategory=3&culture=uk-US&CountryCode=US


12

Це смішно. Немає вагомих причин забороняти фор-цикл, продуктивність чи інше.

Перегляньте в блозі Джона Скіта показник ефективності та інші аргументи.


2
Оновлено посилання: codeblog.jonskeet.uk/2009/01/29/…
Метт

Швидша конструкція циклу залежить від того, що потрібно повторити. Ще один блог, який орієнтує на кілька ітерацій для декількох видів об’єктів , таких як DataRows та користувацькі об’єкти. Він також включає в себе продуктивність циклу while, а не лише конструкції циклу for і foreach.
Безкоштовний кодер 24

11

У випадках, коли ви працюєте з колекцією предметів, foreachкраще, але якщо ви збільшуєте число, afor цикл краще.

Зауважте, що в останньому випадку ви можете зробити щось на кшталт:

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

Але це, безумовно, не працює краще, насправді має гірші показники порівняно з for.


"Краще" - це дискусія: це повільніше, і налагоджувач dnspy ​​не вріжеться в передбачення C # (хоча налагоджувач VS2017 буде). Іноді зрозуміліше, але якщо ви підтримуєте мови без цього, це може бути прикро.
Zeek2

10

Це повинно врятувати вас:

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

Використання:

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

Для більшої виграші ви можете взяти в якості параметрів трьох делегатів.


1
Одна невелика різниця полягає в тому, що forцикл, як правило, пишеться, щоб виключити кінець діапазону (наприклад 0 <= i < 10). Parallel.Forтакож робить це, щоб він був легко взаємозамінним із загальним forциклом.
Гроо

9

ви можете прочитати про це у Deep .NET - частина 1 Ітерація

він охоплює результати (без першої ініціалізації) з вихідного коду .NET аж до розбирання.

наприклад - Ітерація масиву з циклом foreach: введіть тут опис зображення

і - перерахуйте ітерацію циклу foreach: введіть тут опис зображення

і кінцеві результати: введіть тут опис зображення

введіть тут опис зображення


8

Різниці в швидкості в for- і foreach-loop невеликі, коли ви переглядаєте загальні структури, такі як масиви, списки тощо, і LINQзапити над колекцією майже завжди трохи повільніше, хоча писати приємніше! Як казали інші афіші, скоріше скористайтеся виразністю, а не мілісекундами додаткової продуктивності.

Досі не сказано, що коли foreachкомпілюється цикл, його оптимізує компілятор на основі колекції, над якою він повторюється. Це означає, що коли ви не впевнені, який цикл використовувати, вам слід скористатися foreachциклом - він створить найкращий цикл для вас, коли він буде складений. Це також читабельніше.

Ще однією ключовою перевагою foreachциклу є те, що якщо реалізація вашої колекції зміниться (від int arrayдо aList<int> наприклад), то ваш foreachцикл не потребує змін коду:

foreach (int i in myCollection)

Вище , не одне і те ж незалежно від того , якого типу вашої колекції є, в той час як у вашому forциклі, наступний не будуватиме , якщо ви змінили myCollectionвід arrayдо List:

for (int i = 0; i < myCollection.Length, i++)

7

"Чи є якісь аргументи, які я міг би використати, щоб переконати його у тому, що цикл for прийнятний для використання?"

Ні, якщо ваш начальник керує мікроконтролем до рівня того, щоб сказати вам, які конструкції мови програмування використовувати, ви дійсно нічого не можете сказати. Вибачте.


7

Це, мабуть, залежить від типу колекції, яку ви перераховуєте, та реалізації її індексатора. Взагалі, хоча, використовуючиforeach , ймовірно, буде кращим підходом.

Крім того, вона працюватиме з будь-якими IEnumerable- не лише з речами з індексаторами.


7

На це є два варіанти відповіді, як і на більшість питань "швидше":

1) Якщо ви не вимірюєте, ви не знаєте.

2) (Тому що ...) Це залежить.

Це залежить від того, наскільки дорогий метод "MoveNext ()", відносно того, наскільки дорогий метод "цього [int index]", для типу (або типів) IEnumerable, який ви будете повторювати.

Ключове слово "foreach" - це скорочення для ряду операцій - воно викликає GetEnumerator () один раз на IEnumerable, воно викликає MoveNext () один раз за ітерацію, робить деяку перевірку типу тощо. Найімовірніше, що вплине на вимірювання продуктивності - це вартість MoveNext (), оскільки вона викликає O (N) разів. Можливо, це дешево, але, можливо, це не так.

Ключове слово "для" виглядає більш передбачувано, але всередині більшості "для" циклів ви знайдете щось на зразок "колекція [індекс]". Це схоже на просту операцію індексації масиву, але насправді це виклик методу, вартість якого повністю залежить від характеру колекції, над яким ви повторюєте. Напевно, це дешево, але, можливо, це не так.

Якщо основна структура колекції по суті є пов'язаним списком, MoveNext є дешевим, але індексатор може мати вартість O (N), що робить справжню вартість циклу "for" O (N * N).


6

Кожна мовна конструкція має відповідний час та місце для використання. Існує причина, що мова C # має чотири окремі заяви про ітерацію - кожне існує для певної мети і має відповідне використання.

Я рекомендую сісти з вашим начальником і спробувати раціонально пояснити, чому forцикл має мету. Бувають випадки, коли forблок ітерації більш чітко описує алгоритм, ніж foreachітерацію. Коли це правда, їх доцільно використовувати.

Я б також зазначив вашому начальникові. Продуктивність не є, і це не повинно бути проблемою в будь-якому практичному відношенні - це більше питання вираження алгоритму в стислій, осмисленій, ремонтованій манері. Мікрооптимізація на зразок цієї програми повністю втрачає точку оптимізації продуктивності, оскільки будь-яка реальна користь від продуктивності буде випливати з алгоритмічного перепроектування та рефакторингу, а не реструктуризації циклу.

Якщо після раціональної дискусії все ще існує ця авторитарна думка, то ви вирішувати, як діяти далі. Особисто я не був би радий працювати в умовах, де раціональна думка не заважає, і я міг би перейти на іншу посаду в іншого роботодавця. Однак я настійно рекомендую обговоритись до того, як засмучуватися - може просто виникнути просте непорозуміння.


5

Саме те, що ви робите всередині циклу, впливає на ефективність, а не на власне контурну конструкцію (якщо припустити, що ваш випадок нетривіальний).


5

Чи forшвидше, ніж foreachнасправді, крім пункту. Я серйозно сумніваюся, що вибір одного над іншим суттєво вплине на вашу ефективність.

Найкращий спосіб оптимізувати вашу програму - через профілювання фактичного коду. Це точно визначить методи, на які припадає найбільше роботи / часу. Оптимізуйте їх першими. Якщо продуктивність все ще не прийнятна, повторіть процедуру.

Як правило, я б рекомендував уникати мікрооптимізацій, оскільки вони рідко принесуть значні переваги. Винятком є ​​лише оптимізація ідентифікованих гарячих шляхів (тобто, якщо ваше профілювання виявляє декілька високо використовуваних методів, може бути сенсом їх широко оптимізувати).


Якби єдиний вид оптимізації, який мені потрібно було зробити в проектах, над якими я працював, - це мікрооптимізація, я би був щасливим відпочивачем. На жаль, це ніколи не буває.
Яннік Мотон

2
forнезначно швидше, ніж foreach. Я серйозно заперечую проти цього твердження. Це повністю залежить від основної колекції. Якщо клас зв'язаного списку надає індексатору цілий параметр, я б очікував, що використання forцикла на ньому буде O (n ^ 2), тоді foreachяк очікується, що буде O (n).
Мехрдад Афшарі

@Merhdad: Насправді це хороший момент. Я тільки думав про звичайний випадок індексації списку (тобто масиву). Я передумаю, щоб це відобразити. Дякую.
Брайан Расмуссен

@Mehrdad Afshari: Індексація колекції цілим числом може бути набагато повільнішою, ніж перерахування над нею. Але ви фактично порівнюєте використання for та пошук індексатора з самим використанням foreach. Я думаю , що відповідь @ Брайан Расмуссен правильно , що, крім будь-якого використання колекції, forзавжди буде трохи швидше foreach. Однак forплюс пошук колекції завжди буде повільнішим, ніж foreachсам по собі.
Даніель Приден

@Daniel: Або у вас є звичайний масив, для якого обидва будуть генерувати однаковий код, або там використовується індексатор, коли ви використовуєте forоператор. Звичайна forпетля з цілочисельною змінною керування не порівнянна foreach, тому це не вдається . Я розумію, що означає @ Brian, і це правильно, як ви говорите, але відповідь може бути оманливою. Чи не Re: Ваш останній момент: немає, на самому ділі, в forпротягом List<T>ще швидше foreach.
Мехрдад Афшарі

4

Двоє будуть бігати майже точно так само. Напишіть якийсь код, щоб використовувати обидва, а потім покажіть йому ІЛ. Він повинен показувати порівнянні обчислення, тобто не має різниці в продуктивності.


Компілятор розпізнає цикли foreach, що використовуються в масивах / ILists тощо, і змінює їх на цикли.
Каллум Роджерс

3
Покажіть йому рядки нерозбірливого доказу, що це нормально, і попросіть його підтвердити, що це не так.
cjk

3

для має більш просту логіку для реалізації, тому це швидше, ніж foreach.


3

Якщо ви не перебуваєте в конкретному процесі оптимізації швидкості, я б сказав, що використання будь-якого методу створює найпростіший для читання та обслуговування код.

Якщо ітератор вже налаштований, як і в одному з класів колекції, то передбачення - це хороший простий варіант. І якщо це цілий діапазон, який ви повторюєте, то, ймовірно, чистіше.



3

У більшості випадків насправді різниці немає.

Зазвичай вам завжди доводиться використовувати foreach, коли у вас немає явного числового індексу, і ви завжди повинні використовувати його, коли у вас фактично немає ітерабельної колекції (наприклад, повторення над двовимірною сіткою масиву у верхньому трикутнику) . Є деякі випадки, коли у вас є вибір.

Можна стверджувати, що циклів може бути трохи складніше підтримувати, якщо в коді почнуть з'являтися магічні числа. Ви повинні бути роздратовані тим, що не можете використовувати цикл for, і вам потрібно створити колекцію або використовувати лямбда для створення підколекції, тільки тому, що цикли були заборонені.


3

Здається трохи дивним, щоб повністю заборонити використовувати щось подібне до циклу.

Там цікава стаття тут , яка охоплює багато відмінностей в продуктивності між двома петлями.

Особисто я б сказав, що я вважаю, що передбачити трохи зручніше для циклів, але вам слід використовувати найкраще для роботи, і не потрібно писати зайвий довгий код, щоб включити цикл foreach, якщо для циклу є більш підходящим.


Основна цитата складається зі статті, на яку ви посилаєтесь: "... якщо ви плануєте писати високоефективний код, який не призначений для колекцій, використовуйте для циклу. Навіть для колекцій foreach може виглядати зручно при використанні, але це не так ефективно".
NickFitz

3

Я знайшов foreachцикл, який повторюється List швидше . Дивіться мої результати тестування нижче. У наведеному нижче коді я повторюю arrayрозмір 100, 10000 та 100000 окремо за допомогою forта foreachциклу для вимірювання часу.

введіть тут опис зображення

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);
    }

ОНОВЛЕНО

Після пропозиції @jgauffin я використав код @johnskeet і виявив, що forцикл із ним arrayшвидше, ніж наступний,

  • Процідіть петлю з масивом.
  • Для циклу зі списком.
  • Процідіть петлю зі списком.

Дивіться мої результати тесту та код нижче,

введіть тут опис зображення

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);
    }

3
Це дуже поганий тест. а) ви робите занадто мало ітерацій, щоб отримати остаточну відповідь; b) що Thread.Sleep насправді не чекатиме однієї мілісекунди. Використовуйте той самий метод, що й Джон Скіт у своїй відповіді.
jgauffin

1
99,99% часу визначено витрачається в потоці. Цикл дуже швидкий, а сон - дуже повільний, ви не використовуєте пізніше для перевірки першого.
Ронан Тібаудау

3

Ви можете справді закрутити його головою і замість цього зайти на IQueryable.

myList.ForEach(c => Console.WriteLine(c.ToString());

3
Я б замінив ваш рядок коду на myList.ForEach(Console.WriteLine).
Мехрдад Афшарі

2

Я б не очікував, що хтось знайде "величезну" різницю у виконанні між ними.

Я думаю, що відповідь залежить від того, чи має колекція, до якої ви намагаєтесь отримати доступ, більш швидку реалізацію доступу до індексатора чи швидшу реалізацію доступу IEnumerator. Оскільки IEnumerator часто використовує індексатор і просто містить копію поточної позиції індексу, я б очікував, що доступ до перерахунку буде принаймні таким же повільним або повільнішим, ніж прямий доступ до індексу, але не набагато.

Звичайно, ця відповідь не враховує жодних оптимізацій, які компілятор може впровадити.


Компілятор C # робить дуже малу оптимізацію, це дійсно залишає це до JITter.
ljs

Ну, JITter - компілятор ... Так?
ЙоханнесH

2

Майте на увазі, що цикл for-loop та foreach-не завжди є рівнозначним. Перелічувачі списків викинуть виняток, якщо список зміниться, але ви не завжди отримаєте це попередження із звичайним циклом. Ви навіть можете отримати інший виняток, якщо список зміниться в неправильний час.


Якщо список змінюється з-під вас, ви не можете розраховувати на те, що перелічувач підтримує цикл foreach, щоб сказати вам це. Він не збирається перевіряти знову після того, як він поверне вам значення, в результаті чого перегони.
hoodaticus
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.