C # - For-loop внутренности - PullRequest
13 голосов
/ 30 июля 2010

быстрый, простой вопрос от меня о циклах for.

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

Тем не менее, моя главная проблема была с ограничителем. Скажем, у нас есть:

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

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

Однако если что-то удивительное вызывается только один раз, тогда я возвращаюсь под свой счастливый камень! :)

Ответы [ 7 ]

21 голосов
/ 30 июля 2010

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

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 ):

A дляОператор выполняется следующим образом:

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

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

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

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

6 голосов
/ 30 июля 2010

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

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

Теперь, если что-то. 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 проверяет границы массивов каждый раз, когда к ним обращаются, и имеет логику для удаления проверки, когда цикл достаточно прост.

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

4 голосов
/ 30 июля 2010

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

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
}
1 голос
/ 30 июля 2010

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

1 голос
/ 30 июля 2010

Каждый раз, когда компилятор извлекает значение чего-то. Замечательно и оценивает его

1 голос
/ 30 июля 2010

something.awesome будет пересматриваться при каждом прохождении цикла.

Было бы лучше сделать это:

0 голосов
/ 30 июля 2010

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

использование (int limit = кое-что. Awesome)
{
для (int i = 0; i {
//Code.
}
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...