C # - For-loop внутренности

13
задан Vectovox 30 July 2010 в 08:23
поделиться

7 ответов

Вы можете использовать простой пример программы для проверки поведения:

using System;

class Program
{
    static int GetUpperBound()
    {
        Console.WriteLine("GetUpperBound called.");
        return 5;
    }

    static void Main(string[] args)
    {
        for (int i = 0; i < GetUpperBound(); i++)
        {
            Console.WriteLine("Loop iteration {0}.", i);
        }
    }
}

Результат следующий:

GetUpperBound called. 
Loop iteration 0. 
GetUpperBound called. 
Loop iteration 1. 
GetUpperBound called. 
Loop iteration 2. 
GetUpperBound called. 
Loop iteration 3. 
GetUpperBound called. 
Loop iteration 4. 
GetUpperBound called.

Подробности этого поведения описаны в Спецификации языка C # 4.0, раздел 8.3.3 (Вы найдете спецификацию внутри C: \ Program Files \ Microsoft Visual Studio 10.0 \ VC # \ Specifications \ 1033 ):

Оператор for выполняется как следует:

  • Если присутствует инициализатор for, инициализаторы переменных или инструкция выражения выполняются в порядке они написаны. Этот шаг только выполняется один раз.

  • Если условие for присутствует, оно оценивается.

  • Если условие for отсутствует или результат оценки верен, управление передается встроенному утверждение. Когда и если управление достигает конечная точка встроенного заявление (возможно, от выполнения оператор continue), выражения фор-итератора, если таковой имеется, являются оценивается последовательно, а затем выполняется еще одна итерация, начиная с оценки условие for на шаге выше.

  • Если условие for присутствует и оценка дает ложь, управление переносится в конечную точку для заявления.

21
ответ дан 1 December 2019 в 19:39
поделиться

Он оценивается каждый раз. Попробуйте это в простом консольном приложении:

public class MyClass
{
    public int Value
    {
        get
        {                
            Console.WriteLine("Value called");
            return 3;
        }
    }
}

Используется таким образом:

MyClass myClass = new MyClass();
for (int i = 0; i < myClass.Value; i++)
{                
}

В результате на экран будут выведены три строки.

Обновление

Чтобы этого избежать, можно сделать следующее:

int awesome = something.awesome;
for(int i = 0; i < awesome; i++)
{
// Do cool stuff
}
4
ответ дан 1 December 2019 в 19:39
поделиться

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

Лучше сделать так:

int limit = something.awesome;
for(int i = 0; i < limit; i++)
{
// Do cool stuff
}
1
ответ дан 1 December 2019 в 19:39
поделиться

Каждый раз компилятор извлекает значение something.awesome и оценивает его

1
ответ дан 1 December 2019 в 19:39
поделиться

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

1
ответ дан 1 December 2019 в 19:39
поделиться

Он будет сохранять некоторую переменную и очищать ее после использования.

с использованием (int limit = something.awesome)
{
для (int i = 0; i {
//Код.
}
}

Таким образом, он не будет проверять каждый раз.

0
ответ дан 1 December 2019 в 19:39
поделиться

Если something.awesome является полем, то, скорее всего, доступ к нему будет осуществляться каждый раз в цикле, поскольку что-то в теле цикла может его обновить. Если тело цикла достаточно простое и не вызывает никаких методов (кроме методов, вставляемых компилятором), то компилятор может доказать, что безопасно поместить значение something.awesome в регистр. Раньше авторы компиляторов шли на многое, чтобы сделать такую короткую вещь.

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

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

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

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

Изменение кода на:

int limit = something.awesome; 
for(int i = 0; i < limit; i++) 
{ 
// Do cool stuff 
}

в некоторых случаях разгонит его, но также сделает его более сложным. Однако

int limit = myArray.length; 
for(int i = 0; i < limit; i++) 
{ 
   myArray[i[ = xyn;
}

будет медленнее, чем

for(int i = 0; i < myArray.length; i++) 
{ 
   myArray[i[ = xyn;
}

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

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

6
ответ дан 1 December 2019 в 19:39
поделиться
Другие вопросы по тегам:

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