В .NET использование «foreach» для итерации экземпляра IEnumerable <ValueType>создаст копию?Так что я должен предпочесть использовать «для» вместо «foreach»? - PullRequest
21 голосов
/ 14 апреля 2011

В .NET использование «foreach» для итерации экземпляра IEnumerable создаст копию? Так стоит ли мне использовать «for» вместо «foreach»?

Я написал код для подтверждения этого:

struct ValueTypeWithOneField
{
    private Int64 field1;
}

struct ValueTypeWithFiveField
{
    private Int64 field1;
    private Int64 field2;
    private Int64 field3;
    private Int64 field4;
    private Int64 field5;
}

public class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("one field");
        Test<ValueTypeWithOneField>();

        Console.WriteLine("-----------");

        Console.WriteLine("Five field");
        Test<ValueTypeWithFiveField>();

        Console.ReadLine();
    }

    static void Test<T>()
    {
        var test = new List<T>();
        for (int i = 0; i < 5000000; i++)
        {
            test.Add(default(T));
        }

        Stopwatch sw = new Stopwatch();

        for (int i = 0; i < 5; i++)
        {
            sw.Start();

            foreach (var item in test)
            {

            }

            sw.Stop();
            Console.WriteLine("foreach " + sw.ElapsedMilliseconds);
            sw.Restart();

            for (int j = 0; j < test.Count; j++)
            {
                T temp = test[j];
            }

            sw.Stop();
            Console.WriteLine("for " + sw.ElapsedMilliseconds);
            sw.Reset();
        }
    }}

И вот результат, который я получил после запуска кода:

    one field
    foreach 68
    for 72
    foreach 68
    for 72
    foreach 67
    for 72
    foreach 64
    for 73
    foreach 68
    for 72
    -----------
    Five field
    foreach 272
    for 193
    foreach 273
    for 191
    foreach 272
    for 190
    foreach 271
    for 190
    foreach 275
    for 188

Как мы видим в результате, «foreach» всегда занимает больше времени, чем «for».

Так что я должен предпочесть использовать "for" вместо "foreach" при переборе общего набора значений типа?

Примечание: спасибо за напоминание, я отредактировал код и результат. но все же foreach работает медленнее, чем для.

Ответы [ 8 ]

26 голосов
/ 14 апреля 2011

Ваш вопрос слишком сложный. Разбей это.

Создает ли использование последовательности foreach для итерации последовательности типов значений копию последовательности?

номер

Создает ли копия foreach при использовании «foreach» для итерации последовательности типов значений?

Да.

Создает ли копия «каждое» с помощью for для эквивалентной итерации индексированной последовательности типов значений?

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

Делает ли что-либо с типом значения, копия значения?

Просто о. Типы значений копируются по значению . Вот почему они называются типами значений. Единственные вещи, которые вы делаете с типами значений, которые не копируют, - это вызовы методов для типа значения и передача переменной типа значения с помощью «out» или «ref». Типы значений копируются постоянно ; именно поэтому типы значений часто медленнее, чем ссылочные типы.

Копирует ли ссылка использование foreach или for для итерации последовательности ссылочного типа?

Да. Значением выражения ссылочного типа является ссылка. Эта ссылка копируется всякий раз, когда она используется.

Так в чем же разница между типами значений и ссылочными типами с точки зрения их поведения при копировании?

Типы значений копируются по значению. Типы ссылок копируют ссылку, но не указанную вещь. 16-байтовый тип значения копирует 16 байтов каждый раз, когда вы его используете. Тип ссылки 16 байтов копирует ссылку 4 (или 8) байтов каждый раз, когда вы используете ее.

Является ли цикл foreach медленнее, чем цикл for?

Часто это так. Цикл foreach часто выполняет больше работы, поскольку он создает перечислитель и вызывает методы перечислителя, а не просто увеличивает целое число. Целочисленные приращения чрезвычайно быстры. Также не забывайте, что перечислитель в цикле foreach должен быть расположен, и это также может занять время.

Стоит ли использовать цикл for вместо цикла foreach, поскольку цикл for иногда выполняется на несколько микросекунд быстрее?

Нет. Это глупо. Вы должны принимать продуманные инженерные решения на основе ориентированных на клиента эмпирических данных. Дополнительное бремя цикла foreach крошечно. Клиент, вероятно, никогда не заметит. Что вы должны сделать, это:

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

Шансы чрезвычайно хороши: если у вас проблемы с производительностью, изменение цикла foreach на цикл for не будет иметь никакого значения для вашей проблемы. Сначала напишите код так, чтобы он выглядел ясным и понятным.

13 голосов
/ 14 апреля 2011

Ваш тест не точный; в версии foreach вы фактически раскручиваете перечислитель и извлекаете каждое значение из списка (даже если вы его не используете). В версии for вы ничего не делаете со списком, кроме просмотра его свойства Count. По сути, вы тестируете производительность перечислителя, проходящего через коллекцию, по сравнению с увеличением целочисленной переменной эквивалентное число раз.

Чтобы создать четность, вам нужно объявить временную переменную и назначить ее на каждой итерации цикла for.

При этом ответ на ваш вопрос да . Копия значения будет создаваться с каждым присваиванием или оператором return.

Производительность

Эта разбивка псевдокода должна объяснить, почему foreach несколько медленнее, чем for в данном конкретном случае:

foreach

try
{
    var en = test.GetEnumerator(); //creates a ListEnumerator
    T item;

    while(en.MoveNext()) // MoveNext increments the current index and returns
                         // true if the new index is valid, or false if it's
                         // beyond the end of the list. If it returns true,
                         // it retrieves the value at that index and holds it 
                         // in an instance variable
    {
        item = en.Current; // Current retrieves the value of the current instance
                           // variable
    }
}
finally { }

for:

int index = -1;
T item;

while(++index < test.Count)
{
    item = test[index];
}

Как видите, в реализации for кода меньше, а foreach имеет слой абстракции (перечислитель) поверх for. Я написал for, используя цикл while, чтобы показать две версии в похожем представлении.

Со всем сказанным ...

Вы говорите о тривиальной разнице во времени исполнения. Используйте цикл, который делает код понятнее и меньше, и в этом случае выглядит как foreach.

3 голосов
/ 14 апреля 2011

Вы не сбрасываете «секундомер» после теста «для» , поэтому время, необходимое для теста «для», добавляется к последующему тесту «foreach». Также, как правильно указано, вы должны выполнить присваивание внутри 'for', чтобы имитировать точное поведение foreach.

sw.Start();

foreach (var item in test)
{

}

sw.Stop();
Console.WriteLine("foreach " + sw.ElapsedMilliseconds);
sw.Restart();

for (int j = 0; j < test.Count; j++)
{
    T temp = test[j];
}

sw.Stop();
Console.WriteLine("for " + sw.ElapsedMilliseconds);
sw.Reset(); // -- This bit is missing!
2 голосов
/ 14 апреля 2011

В вашем цикле for я не вижу, чтобы вы на самом деле обращались к предметам из test. Если вы добавите var x = test[i]; в цикл for, вы увидите, что производительность будет (практически) одинаковой.

Каждый доступ к свойству типа значения создает копию либо с foreach, либо с использованием indexer в list в цикле for.

1 голос
/ 14 апреля 2011

Я нашел только один случай, когда это имеет значение - Разработка для Windows Phone 7. Есть две причины, по которым следует изменить

foreach(var item in colletion)
{
}

Для

int length = array.Length;
for(int i = 0; i < length; ++i)
{
}

в игре XNA, если коллекции большие или их часто называют (например, метод обновления).

  • Это немного быстрее
  • меньше мусора

и мусор имеет решающее значение, так как Compact Framework GC запускает каждое выделение 1 МБ, в результате это может вызвать раздражающие зависания.

1 голос
/ 14 апреля 2011

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

1 голос
/ 14 апреля 2011

Ваш тест не честен.Посмотрите, как работает цикл foreach.У вас есть следующий код:

foreach (var item in test)
{

}

Это создает переменную item, и на каждой итерации выбирает следующий объект из коллекции и присваивает его item.Эта выборка и назначение не должны создавать копию, но для доступа к основной коллекции и присвоения правильного значения переменной требуется время.

Тогда у вас есть этот код:

for (int j = 0; j < test.Count; j++)
{

}

Это не дает доступа к базовой коллекции вообще.Он не читает и не присваивает переменную на каждой итерации.Он просто увеличивает целое число test.Count раз, поэтому, конечно, он быстрее.И если компилятор умен, он увидит, что в цикле не происходит никаких операций, и просто все оптимизирует.

Справедливое сравнение заменит этот второй бит кода на что-то вроде:

var item;
for (int j = 0; j < test.Count; j++)
{
    item = test.get(j);
} 

Это более сопоставимо с тем, что делает ваш цикл foreach.

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

1 голос
/ 14 апреля 2011
...