Разница в производительности для структур управления 'for' и 'foreach' в C #

Какой фрагмент кода даст лучшую производительность? Ниже сегменты кода были написаны на C #.

1.

for(int counter=0; counter<list.Count; counter++)
{
    list[counter].DoSomething();
}

2.

foreach(MyType current in list)
{
    current.DoSomething();
}
 Trillian14 июл. 2009 г., 13:54
Этот второй код не скомпилируется. В System.Object нет члена с именем «значение»; (если вы действительно не злы, не определили его как метод расширения и сравниваете делегатов). Сильно наберите свой foreach.
 Jon Skeet14 июл. 2009 г., 13:59
Первый код также не будет скомпилирован, если только типlist действительно естьcount член вместоCount.
 Fortyrunner14 июл. 2009 г., 13:23
Если бы ваше приложение не очень критично для производительности, я бы об этом не беспокоился. Намного лучше иметь чистый и легко понятный код.
 darasd14 июл. 2009 г., 13:19
Я полагаю, что это не имеет значения. Если у вас проблемы с производительностью, это почти наверняка не из-за этого. Не то чтобы вы не задавали вопрос ...
 Ed James14 июл. 2009 г., 13:44
Меня беспокоит, что некоторые ответы здесь, похоже, размещены людьми, которые просто не имеют понятия итератора нигде в своем мозгу, и, следовательно, не имеют понятия перечислителей или указателей.

Ответы на вопрос(9)

Вы можете прочитать об этом вDeep .NET - часть 1 итерация

он охватывает результаты (без первой инициализации) исходного кода .NET вплоть до разборки.

например - Итерация массива с циклом foreach: enter image description here

и - список итераций с циклом foreach: enter image description here

и конечные результаты: enter image description here

enter image description here

for цикл компилируется в код, примерно эквивалентный этому:

int tempCount = 0;
while (tempCount < list.Count)
{
    if (list[tempCount].value == value)
    {
        // Do something
    }
    tempCount++;
}

Где какforeach цикл компилируется в код, примерно эквивалентный этому:

using (IEnumerator<T> e = list.GetEnumerator())
{
    while (e.MoveNext())
    {
        T o = (MyClass)e.Current;
        if (row.value == value)
        {
            // Do something
        }
    }
}

Итак, как вы можете видеть, все будет зависеть от того, как реализован перечислитель, от того, как реализован индексатор списков. Как оказалось, перечислитель для типов, основанных на массивах, обычно пишется примерно так:

private static IEnumerable<T> MyEnum(List<T> list)
{
    for (int i = 0; i < list.Count; i++)
    {
        yield return list[i];
    }
}

Итак, как вы можете видеть, в этом случае это не будет иметь большого значения, однако перечислитель для связанного списка, вероятно, будет выглядеть примерно так:

private static IEnumerable<T> MyEnum(LinkedList<T> list)
{
    LinkedListNode<T> current = list.First;
    do
    {
        yield return current.Value;
        current = current.Next;
    }
    while (current != null);
}

В.СЕТЬ вы обнаружите, что LinkedList & lt; T & gt; класс даже не имеет индексатора, поэтому вы не сможете выполнить цикл for в связанном списке; но если бы вы могли, индексатор должен был бы быть написан так:

public T this[int index]
{
       LinkedListNode<T> current = this.First;
       for (int i = 1; i <= index; i++)
       {
            current = current.Next;
       }
       return current.value;
}

Как вы можете видеть, многократный вызов этого цикла будет намного медленнее, чем использование перечислителя, который может запомнить, где он находится в списке.

В приведенном вами примере лучше использоватьforeach цикл вместоfor петля.

Стандартforeach Конструкция может быть быстрее (1,5 цикла на шаг), чем простаяfor-loop (2 цикла на шаг), если только цикл не был развернут (1,0 цикла на шаг).

Таким образом, для повседневного кода производительность не является причиной для использования более сложногоfor, while или жеdo-while строит.

Проверьте эту ссылку:http://www.codeproject.com/Articles/146797/Fast-and-Less-Fast-Loops-in-C

╔══════════════════════╦═══════════╦═══════╦════════════════════════╦═════════════════════╗
║        Method        ║ List<int> ║ int[] ║ Ilist<int> onList<Int> ║ Ilist<int> on int[] ║
╠══════════════════════╬═══════════╬═══════╬════════════════════════╬═════════════════════╣
║ Time (ms)            ║ 23,80     ║ 17,56 ║ 92,33                  ║ 86,90               ║
║ Transfer rate (GB/s) ║ 2,82      ║ 3,82  ║ 0,73                   ║ 0,77                ║
║ % Max                ║ 25,2%     ║ 34,1% ║ 6,5%                   ║ 6,9%                ║
║ Cycles / read        ║ 3,97      ║ 2,93  ║ 15,41                  ║ 14,50               ║
║ Reads / iteration    ║ 16        ║ 16    ║ 16                     ║ 16                  ║
║ Cycles / iteration   ║ 63,5      ║ 46,9  ║ 246,5                  ║ 232,0               ║
╚══════════════════════╩═══════════╩═══════╩════════════════════════╩═════════════════════╝
 25 февр. 2014 г., 05:53
Вы можете перечитать статью проекта кода, которую вы указали. Это интересная статья, но она говорит прямо напротив вашего поста. Кроме того, таблица, которую вы воссоздали, измеряет производительность доступа к массиву и списку напрямую или через их интерфейсы IList. Ни один из них не имеет ничего общего с вопросом. :)

Есть еще один интересный факт, который можно легко пропустить при тестировании скорости обоих циклов: Использование режима отладки не позволяет компилятору оптимизировать код с использованием настроек по умолчанию.

Это привело меня к интересному результату: foreach работает быстрее, чем в режиме отладки. Принимая во внимание, что он быстрее, чем foreach в режиме релиза. Очевидно, что у компилятора есть лучшие способы оптимизировать цикл for, чем цикл foreach, который компрометирует несколько вызовов методов. Цикл for, между прочим, настолько фундаментален, что возможно, что он оптимизируется даже самим процессором.

После прочтения достаточного количества аргументов, что «цикл foreach должен быть предпочтительным для читабельности», я могу сказать, что моей первой реакцией было «что»? Читаемость, в общем, субъективна и, в данном конкретном случае, даже больше. Для тех, кто имеет опыт программирования (практически все языки до Java), циклы for гораздо легче читать, чем циклы foreach. Кроме того, те же люди, которые утверждают, что циклы foreach более читабельны, также поддерживают Linux и другие «функции». которые делают код трудным для чтения и поддержки, что доказывает вышеизложенное.

О влиянии на производительность см. Ответ наэтот вопрос.

РЕДАКТИРОВАТЬ: Есть коллекции в C # (например, HashSet), которые не имеют индексатора. В этих коллекцияхforeach это единственный способ итерации, и это единственный случай, я думаю, его следует использовать болееfor.

Простой тест для полу-проверки. Я сделал небольшой тест, просто чтобы посмотреть. Вот код:

static void Main(string[] args)
{
    List<int> intList = new List<int>();

    for (int i = 0; i < 10000000; i++)
    {
        intList.Add(i);
    }

    DateTime timeStarted = DateTime.Now;
    for (int i = 0; i < intList.Count; i++)
    {
        int foo = intList[i] * 2;
        if (foo % 2 == 0)
        {
        }
    }

    TimeSpan finished = DateTime.Now - timeStarted;

    Console.WriteLine(,finished.TotalMilliseconds.ToString());
    Console.Read();

}

А вот раздел foreach:

foreach (int i in intList)
{
    int foo = i * 2;
    if (foo % 2 == 0)
    {
    }
}

Когда я заменил for на foreach - он был на 20 миллисекунд быстрее -consistently, Значение for составляло 135-139 мс, а значение foreach - 113-119 мс. Я несколько раз обменивался взад-вперед, убедившись, что это не какой-то процесс, который только что начался.

Однако когда я удалил оператор foo и if, for был быстрее на 30 мс (foreach был 88 мс, а for был 59 мс). Они оба были пустыми снарядами. Я предполагаю, что foreach фактически передал переменную, где for просто увеличивал переменную. Если бы я добавил

int foo = intList[i];

Затем скорость становится медленной примерно на 30 мс. Я предполагаю, что это связано с созданием foo, захватом переменной в массиве и присвоением ее foo. Если вы просто обращаетесь к intList [i], то у вас нет такого штрафа.

Честно говоря ... Я ожидал, что foreach будет немного медленнее при любых обстоятельствах, но не достаточным, чтобы иметь значение в большинстве приложений.

edit: вот новый код, использующий предложения Jons (134217728 - это самое большое int, которое вы можете получить до того, как будет сгенерировано исключение System.OutOfMemory):

static void Main(string[] args)
{
    List<int> intList = new List<int>();

    Console.WriteLine("Generating data.");
    for (int i = 0; i < 134217728 ; i++)
    {
        intList.Add(i);
    }

    Console.Write("Calculating for loop:\t\t");

    Stopwatch time = new Stopwatch();
    time.Start();
    for (int i = 0; i < intList.Count; i++)
    {
        int foo = intList[i] * 2;
        if (foo % 2 == 0)
        {
        }
    }

    time.Stop();
    Console.WriteLine(time.ElapsedMilliseconds.ToString() + "ms");
    Console.Write("Calculating foreach loop:\t");
    time.Reset();
    time.Start();

    foreach (int i in intList)
    {
        int foo = i * 2;
        if (foo % 2 == 0)
        {
        }
    }

    time.Stop();

    Console.WriteLine(time.ElapsedMilliseconds.ToString() + "ms");
    Console.Read();
}

И вот результаты:

Генерация данных. Расчет по петле: 2458м, с Расчет цикла foreach: 2005мс

Меняя местами их, чтобы увидеть, имеет ли он дело с порядком вещей, можно получить те же результаты (почти).

 14 июл. 2009 г., 15:36
Лучше использовать секундомер, чем DateTime.Now - и я бы не стал доверять так быстро, если честно.
 16 июн. 2015 г., 18:47
После компиляции for и foreach оптимизируют одно и то же при работе с примитивами. Это не до тех пор, пока вы не введете Список & lt; T & gt; Что они отличаются (сильно) по скорости.
 27 февр. 2012 г., 20:42
Ваши циклы foreach работают быстрее, потому что «для» оценивает условие каждой итерации. В нашем примере это делает один дополнительный вызов метода (чтобы получить list.count). Короче говоря, вы тестируете два разных куска кода, отсюда и ваши странные результаты. Попробуйте & int; max = intlist.Count; for (int i = 0; i & lt; max; i ++) ... ' и "для"; Цикл всегда будет работать быстрее, как и ожидалось!

Note: this answer applies more to Java than it does to C#, since C# doesn't have an indexer on LinkedLists, but I think the general point still holds.

Еслиlist вы работаете сLinkedListПроизводительность индекс-кода (array-style доступ) намного хуже, чем при использованииIEnumerator отforeach, для больших списков.

При доступе к элементу 10.000 вLinkedList используя синтаксис индексатора:list[10000]связанный список будет начинаться с головного узла и проходить черезNextуказатель десять тысяч раз, пока он не достигнет нужного объекта. Очевидно, что если вы сделаете это в цикле, вы получите:

list[0]; // head
list[1]; // head.Next
list[2]; // head.Next.Next
// etc.

Когда вы звонитеGetEnumerator (неявно используяforach-синтаксис), вы получитеIEnumerator объект, который имеет указатель на головной узел. Каждый раз, когда вы звонитеMoveNextэтот указатель перемещается на следующий узел, вот так:

IEnumerator em = list.GetEnumerator();  // Current points at head
em.MoveNext(); // Update Current to .Next
em.MoveNext(); // Update Current to .Next
em.MoveNext(); // Update Current to .Next
// etc.

Как вы можете видеть, в случаеLinkedLists, метод индексирования массива становится медленнее и медленнее, чем дольше вы зацикливаетесь (он должен снова и снова проходить один и тот же указатель головы) В то время какIEnumerable просто работает в постоянном времени.

Конечно, как сказал Джон, это действительно зависит от типаlistеслиlist это неLinkedList, но массив, поведение совершенно другое.

 14 июл. 2009 г., 15:53
Ну, это решает эту проблему, тогда :-) Я просто смотрю черезLinkedList<T> документы на MSDN, и он имеет довольно приличный API. Наиболее важно, что он не имеетget(int index) метод, как это делает Java. Тем не менее, я предполагаю, что точка все еще остается в силе для любой другой подобной списку структуры данных, которая предоставляет индексатор, который медленнее, чем определенныйIEnumerator.
 14 июл. 2009 г., 15:35
LinkedList в .NET не имеет индексатора, поэтому на самом деле это не вариант.

Как упоминали другие люди, хотя производительность на самом деле не имеет большого значения, foreach всегда будет немного медленнее из-заIEnumerable/IEnumerator Использование в цикле. Компилятор переводит конструкцию в вызовы этого интерфейса, и для каждого шага функция + свойство вызывается в конструкции foreach.

IEnumerator iterator = ((IEnumerable)list).GetEnumerator();
while (iterator.MoveNext()) {
  var item = iterator.Current;
  // do stuff
}

Это эквивалентное расширение конструкции в C #. Вы можете представить, как влияние на производительность может варьироваться в зависимости от реализации MoveNext и Current. В то время как при доступе к массиву у вас нет этих зависимостей.

 14 июл. 2009 г., 18:59
Очень верно! Это еще одно свойство исполнения, и мы находимся во власти реализации.
 14 июл. 2009 г., 13:47
Не забывайте, что есть разница между доступом к массиву и доступом к индексатору. Если списокList<T> вот тогда все еще есть хит (возможно, встроенный) вызова индексатора. Это не похоже на доступ к массиву голого металла.
Решение Вопроса

Ну, это отчасти зависит от точного типаlist, Это также будет зависеть от того, какой именно CLR вы используете.

Является ли это каким-либо образомsignificant или нет, зависит от того, выполняете ли вы какую-либо реальную работу в цикле. Почтиall В этих случаях разница в производительности не будет значительной, но разница в удобочитаемости благоприятствуетforeach петля.

Я лично использую LINQ, чтобы избежать "if" тоже:

foreach (var item in list.Where(condition))
{
}

РЕДАКТИРОВАТЬ: Для тех из вас, кто утверждает, что итерации поList<T> сforeach выдает тот же код, что иfor цикл, вот доказательство того, что это не так:

static void IterateOverList(List<object> list)
{
    foreach (object o in list)
    {
        Console.WriteLine(o);
    }
}

Производит IL из:

.method private hidebysig static void  IterateOverList(class [mscorlib]System.Collections.Generic.List`1<object> list) cil managed
{
  // Code size       49 (0x31)
  .maxstack  1
  .locals init (object V_0,
           valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object> V_1)
  IL_0000:  ldarg.0
  IL_0001:  callvirt   instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<object>::GetEnumerator()
  IL_0006:  stloc.1
  .try
  {
    IL_0007:  br.s       IL_0017
    IL_0009:  ldloca.s   V_1
    IL_000b:  call       instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object>::get_Current()
    IL_0010:  stloc.0
    IL_0011:  ldloc.0
    IL_0012:  call       void [mscorlib]System.Console::WriteLine(object)
    IL_0017:  ldloca.s   V_1
    IL_0019:  call       instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object>::MoveNext()
    IL_001e:  brtrue.s   IL_0009
    IL_0020:  leave.s    IL_0030
  }  // end .try
  finally
  {
    IL_0022:  ldloca.s   V_1
    IL_0024:  constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object>
    IL_002a:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_002f:  endfinally
  }  // end handler
  IL_0030:  ret
} // end of method Test::IterateOverList

Компилятор лечитarrays по-другому, преобразованиеforeach цикл в основномfor петля, но неList<T>, Вот эквивалентный код для массива:

static void IterateOverArray(object[] array)
{
    foreach (object o in array)
    {
        Console.WriteLine(o);
    }
}

// Compiles into...

.method private hidebysig static void  IterateOverArray(object[] 'array') cil managed
{
  // Code size       27 (0x1b)
  .maxstack  2
  .locals init (object V_0,
           object[] V_1,
           int32 V_2)
  IL_0000:  ldarg.0
  IL_0001:  stloc.1
  IL_0002:  ldc.i4.0
  IL_0003:  stloc.2
  IL_0004:  br.s       IL_0014
  IL_0006:  ldloc.1
  IL_0007:  ldloc.2
  IL_0008:  ldelem.ref
  IL_0009:  stloc.0
  IL_000a:  ldloc.0
  IL_000b:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0010:  ldloc.2
  IL_0011:  ldc.i4.1
  IL_0012:  add
  IL_0013:  stloc.2
  IL_0014:  ldloc.2
  IL_0015:  ldloc.1
  IL_0016:  ldlen
  IL_0017:  conv.i4
  IL_0018:  blt.s      IL_0006
  IL_001a:  ret
} // end of method Test::IterateOverArray

Интересно, что я нигде не могу найти это в спецификации C # 3 ...

 14 июл. 2009 г., 13:56
Из интереса, Джон, сценарий со списком & lt; T & gt; выше ... это относится и к другим коллекциям? Кроме того, как вы узнали об этом (без всякого злого умысла) ... как ... вы буквально наткнулись на это, пытаясь ответить на этот вопрос, ранее некоторое время назад? Это так ... случайно / секретно :)
 14 июл. 2009 г., 13:59
Некоторое время я знал об оптимизации массивов - массивы являются «ядром»; вид коллекции; компилятор C # уже глубоко знает о них, поэтому имеет смысл рассматривать их по-разному. Компилятор не имеет (и не должен) никаких специальных знаний оList<T>.
 14 июл. 2009 г., 14:06
Приветствия :) и да ... массивы были первой концепцией сбора, которой меня учили годы и годы назад в универе ... так что было бы понятно, что компилятор достаточно умен, чтобы иметь дело с одним из (если не самым) самым примитивным тип коллекции. ура снова!
 11 мая 2012 г., 09:08
@VeeKeyBee: Так сказал Microsoft в 2004 году. А) все меняется; б) работу придется делатьtiny объемы работы на каждой итерации, чтобы это было значительным. Обратите внимание, чтоforeach по массиву эквивалентноfor тем не мение.Always сначала код для удобочитаемости, затем только микрооптимизация, когда у вас естьevidence что это дает ощутимый выигрыш в производительности.
 06 дек. 2010 г., 18:30
@JonSkeet Оптимизация итератора списка приводит к изменению поведения при изменении списка во время итерации. Вы теряете исключение, если изменено. Оптимизация все еще возможна, но требует проверки того, что никаких изменений не происходит (я полагаю, в том числе и в других потоках).

Ваш ответ на вопрос