Различие в производительности для управляющих структур 'для' и 'foreach' в C#

Дополнительное примечание к get_python_lib функция, упомянутая уже: на некоторых платформах различные каталоги используются для платформы определенные модули (например: модули, которые требуют компиляции). Если Вы передаете plat_specific=True функции, Вы получаете пакеты сайта для платформы определенные пакеты.

103
задан 0lukasz0 9 January 2013 в 19:53
поделиться

5 ответов

Well, it partly depends on the exact type of list. It will also depend on the exact CLR you're using.

Whether it's in any way significant or not will depend on whether you're doing any real work in the loop. In almost all cases, the difference to performance won't be significant, but the difference to readability favours the foreach loop.

I'd personally use LINQ to avoid the "if" too:

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

EDIT: For those of you who are claiming that iterating over a List with foreach produces the same code as the for loop, here's evidence that it doesn't:

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

Produces IL of:

.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

The compiler treats arrays differently, converting a foreach loop basically to a for loop, but not List. Here's the equivalent code for an array:

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

Interestingly, I can't find this documented in the C# 3 spec anywhere...

130
ответ дан 24 November 2019 в 04:20
поделиться

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

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 - foreach был на 20 миллисекунд быстрее - последовательно . Значение for составляло 135–139 мс, а значение foreach - 113–119 мс. Я несколько раз менял местами, чтобы убедиться, что это не какой-то процесс, который только что запустился.

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

int foo = intList[i];

, то for станет медленнее примерно на 30 мс. Я предполагаю, что это было связано с созданием foo, захватом переменной в массиве и присвоением ее foo. Расчет для цикла: 2458 мс Вычисление цикла foreach: 2005 мс

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

12
ответ дан 24 November 2019 в 04:20
поделиться

Примечание: этот ответ больше относится к Java, чем к C #, поскольку в C # нет индексатора для LinkedLists , но я думаю, что общая точка зрения все еще сохраняется.

Если список , с которым вы работаете, оказывается LinkedList , производительность кода индексатора (доступ в стиле массива ) является намного хуже, чем использование 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.

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

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

Я получу объект 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.

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

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

Я получу объект 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.

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

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

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

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

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

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

9
ответ дан 24 November 2019 в 04:20
поделиться

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

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

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

2
ответ дан 24 November 2019 в 04:20
поделиться

Это ситуация, когда «научите человека ловить рыбу» - гораздо более полезный подход, чем «дай человеку рыбу». Как только вы поймете основные принципы и API NSDictionary, станет намного проще разработать собственное решение. Вот несколько наблюдений и моментов для обучения:

  • Метод + dictionaryWithObject: forKey: используется для создания NSDictionary с одной парой ключ-значение. Он не будет принимать аргументы, разделенные запятыми, после каждого двоеточия (:) в вызове метода, только один. Чтобы создать словарь с несколькими парами «ключ-значение», используйте один из двух связанных методов: + dictionaryWithObjects: forKeys: , который принимает два объекта NSArray, содержащих значения и ключи, или + dictionaryWithObjectsAndKeys: , который заменяет (объект, ключ, объект, ключ) завершающим аргументом nil .
  • Упростите код создания. Ты не Чтобы создать вещь, нужен миллион локальных переменных. Используйте встроенные аргументы там, где это имеет смысл. Один из способов сделать это - создать свой словарь в коде как NSMutableDictionary , а затем (при необходимости) сделать его неизменяемую копию, вызвав для него -copy . (Помните, что метод копирования возвращает новый объект, который вы должны освободить, чтобы избежать утечки памяти.) Таким образом, вам не нужно иметь переменную для каждого отдельного значения, поэтому вы можете выполнить «одноразовое» создание структуры (s ) на каждом уровне.
  • Используйте + arrayWithObject: для создания NSArray с одним объектом.
  • (Рекомендации по стилю) Никогда не используйте заглавную букву в начале имени переменной, метода или функция. (Обратите внимание, что SO выделяет переменные с заглавными буквами, такие как имена классов. Как оказалось, перечислитель для типов, основанных на массивах, обычно записывается примерно так:

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

    В .NET вы обнаружите, что класс LinkedList даже не имеет индексатора, поэтому вы не сможете выполнить цикл for в связанном списке; но если бы вы могли, индексатор должен был быть написан так:

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

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

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

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

    В .NET вы обнаружите, что класс LinkedList даже не имеет индексатора, поэтому вы не сможете выполнить цикл for в связанном списке; но если бы вы могли, индексатор должен был быть написан так:

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

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

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

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

    В .NET вы обнаружите, что класс LinkedList даже не имеет индексатора, поэтому вы не сможете выполнить цикл for в связанном списке; но если бы вы могли, индексатор должен был быть написан так:

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

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

14
ответ дан 24 November 2019 в 04:20
поделиться
Другие вопросы по тегам:

Похожие вопросы: