Почему цикл for более эффективен, чем цикл for в Unity?

8

Во время разговора с Unity о производительности парень сказал нам избегать for eachцикла, чтобы повысить производительность, и вместо этого использовать обычный цикл for. В чем разница между forи for eachс точки зрения реализации? Что делает for eachнеэффективным?

Имад
источник
1
Помимо подсчета количества элементов в массиве или того, что вы используете, не должно быть большой разницы. Благодаря быстрому поиску в Google я нашел это: stackoverflow.com/questions/3430194/… (Это будет относиться и к Unity, даже если язык другой, это все та же базовая логика, которая применима)
Джон Гамильтон,
2
Я бы добавил: в вашей первой итерации не нужно тонко настраивать производительность, но приоритетом должна быть чистая архитектура и стиль кода. Оптимизируйте (производительность) только тогда, когда в этом есть необходимость ...
Marco
2
У вас есть ссылка на этот разговор?
Vaillancourt
2
В соответствии с этой страницей Unity , for eachпетля итерация ничего, кроме массива генерирует мусор (версии Unity до до 5,5)
Hellium
2
Я вижу голосование, чтобы закрыть это как не по теме, потому что это общее программирование, но так как его спрашивают в контексте Unity в частности, и это поведение изменилось между версиями Unity, я думаю, что здесь стоит оставить открытым для справки Единство разработчиков игр.
DMGregory

Ответы:

13

Цикл foreach якобы создает объект IEnumerator из коллекции, которую вы передаете, и затем просматривает ее. Итак, такой цикл:

foreach(var entry in collection)
{
    entry.doThing();
}

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

var enumerator = collection.GetEnumerator();
while(enumerator.MoveNext()) {
   var entry = enumerator.Current;
   entry.doThing();
}

Если это скомпилировано наивно / напрямую, то этот объект перечислителя размещается в куче (что занимает немного времени), даже если его базовый тип является структурой, называемой боксом. После завершения цикла это распределение становится мусором для последующей очистки, и ваша игра может на мгновение заикаться, если накапливается достаточно мусора, чтобы сборщик мог выполнить полную очистку. Наконец, MoveNextметод и Currentсвойство могут включать больше шагов вызова функции, чем просто захват элемента напрямую collection[i], если компилятор не встроит это действие.

Приведенные выше ссылки на документацию по Unity в Hellium указывают на то, что эта наивная форма - это именно то, что происходит в Unity до версии 5.5 , если выполнять итерации по чему-либо, кроме собственного массива.

В версии 5.6+ (включая версии 2017 года) этот ненужный бокс исчез , и вы не должны испытывать ненужного выделения, если используете какой-либо конкретный тип (например, int[]или List<GameObject>или Queue<T>).

Вы все равно получите распределение, если рассматриваемый метод знает только, что он работает с каким-то IList или другим интерфейсом - в этих случаях он не знает, с каким конкретным перечислителем он работает, поэтому он должен сначала поместить его в объект IEnumerator. ,

Так что есть хороший шанс, что это старый совет, который не так важен в современном развитии Unity. Единство УБС , как мы можем быть немного суеверны о вещах , которые используются , чтобы быть медленными / волосатой в старых версиях, поэтому принимать какую - либо рекомендацию этой формы с зерном соли. ;) Двигатель развивается быстрее, чем наши убеждения по этому поводу.

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

Д.М.Григорий
источник
Просто, чтобы проверить, List<MyCustomType>и IEnumerator<MyCustomType> getListEnumerator() { }хорошо, тогда как метод IEnumerator getListEnumerator() { }не будет.
Draco18s больше не доверяет SE
0

Если a для каждого цикла используется для коллекции или массива объекта (т. Е. Массива всех элементов, отличных от примитивного типа данных), GC (Garbage Collector) вызывается для освобождения пространства ссылочной переменной в конце a для каждого цикла.

foreach (Gameobject temp in Collection)
{
    //Code Do Task
}

Принимая во внимание, что цикл for используется для итерации по элементам с использованием индекса, поэтому примитивный тип данных не влияет на производительность по сравнению с не примитивным типом данных.

for (int i = 0; i < Collection.length; i++){
    //Get reference using index i;
    //Code Do Task
}
Теджас Ясани
источник
У вас есть цитата для этого? tempПеременной здесь может выделяться в стеке, даже если коллекция полна ссылочных типов (так как это просто ссылка на существующие записи уже в куче, не выделяя новую память кучи для хранения экземпляра этого ссылочного типа), так мы не ожидаем, что мусор произойдет здесь. При некоторых обстоятельствах сам перечислитель может быть упакован в кучу и оказаться в куче, но эти случаи применимы и к примитивным типам данных.
DMGregory