Рассчитаны ли пределы для циклов один раз или для каждого цикла? - PullRequest
19 голосов
/ 31 июля 2010

Рассчитывается ли предел в следующем цикле (12332 * 324234) один или каждый раз при запуске цикла?

for(int i=0; i<12332*324234;i++)
{
    //Do something!
}

Ответы [ 8 ]

29 голосов
/ 31 июля 2010

Для этого он рассчитывается один раз или, более вероятно, 0 раз.

Компилятор оптимизирует умножение для вас.

Однако это не всегда так, если у вас есть что-то вроде.

for(int i=0; i<someFunction();i++)
{
    //Do something!
}

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

РЕДАКТИРОВАТЬ : Как сказал MainMa в комментарии, вы в этой ситуации можете устранить расходы, выполнив что-то вроде этого:

int limit = someFunction();
for(int i=0; i<limit ;i++)
{
    //Do something!
}

IF Вы уверены, что значение someFunction() не изменится в течение цикла.

14 голосов
/ 31 июля 2010

Это одно из наиболее часто неправильно понятых поведений циклов в C #.

Вот что вам нужно знать:

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

Так, например:

for( int i = 0; i < 1234*1234; i++ ) { ... }

В этом случае выражение 1234*1234 является постоянной времени компиляции, и в результате не будет пересчитываться на каждой итерации. Фактически он вычисляется во время компиляции и заменяется константой.

Однако в этом случае:

int k = 10;
for( int i = 0; i < k; i++ ) { k -= 1; ... }

Значение k необходимо проверять на каждой итерации. Ведь это может измениться .. в этом примере делает. К счастью, поскольку k является просто локальной переменной, стоимость доступа к ней очень низка - и во многих случаях она будет либо сохраняться в локальном кэше ЦП, либо, возможно, даже поддерживаться в регистре (в зависимости от того, как JIT обрабатывает и выдает машинный код).

В случае чего-то вроде следующего:

IEnumerable<int> sequence = ...;
for( int i = 0; i < sequence.Count(); i++ ) { ... }

Стоимость вычисления sequence.Count() может быть довольно дорогой. А поскольку он вычисляется на каждой итерации цикла, он может быстро сложиться.

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

IEnumerable<int> sequence = ...;
for( int i = 0; i < sequence.Count(); i++ ) {
    sequence = sequence.Concat( anotherItem );
}

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

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

7 голосов
/ 31 июля 2010

На самом деле это не скомпилируется, потому что переполнится, но если вы сделаете его меньшим числом и откроете Reflector, вы найдете что-то вроде этого.

for (int i = 0; i < 0x3cf7b0; i++)
{

}
3 голосов
/ 31 июля 2010

Существует два способа интерпретации вашего вопроса:

  • Оценивается ли умножение 12332 * 324234 в каждом цикле
  • Оценивается ли выражение в условии цикла в каждом цикле

Ответ на эти два разных вопроса:

  • Нет, фактически он оценивается во время компиляции, поскольку включает две константы
  • Да, если необходимо, они равны

Другими словами:

for (int i = 0; i < someString.Length; i++)

Если оценка someString.Length является дорогостоящей, это повлечет за собой штраф за каждую итерацию цикла.

2 голосов
/ 31 июля 2010

Прежде всего цикл for в вопросе не будет компилироваться. Но допустим, это было

            for (int  i = 0; i < 20; i++)
        {
            Console.WriteLine(i);
            i++;

        }

VS

            for (int  i = 0; i < 10*2; i++)
        {
            Console.WriteLine(i);
            i++;

        }

код IL точно такой же.

   .method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 2
    .locals init (
        [0] int32 i,
        [1] bool CS$4$0000)
    L_0000: nop 
    L_0001: ldc.i4.0 
    L_0002: stloc.0 
    L_0003: br.s L_0016
    L_0005: nop 
    L_0006: ldloc.0 
    L_0007: call void [mscorlib]System.Console::WriteLine(int32)
    L_000c: nop 
    L_000d: ldloc.0 
    L_000e: ldc.i4.1 
    L_000f: add 
    L_0010: stloc.0 
    L_0011: nop 
    L_0012: ldloc.0 
    L_0013: ldc.i4.1 
    L_0014: add 
    L_0015: stloc.0 
    L_0016: ldloc.0 
    L_0017: ldc.i4.s 20
    L_0019: clt 
    L_001b: stloc.1 
    L_001c: ldloc.1 
    L_001d: brtrue.s L_0005
    L_001f: call int32 [mscorlib]System.Console::Read()
    L_0024: pop 
    L_0025: ret 
}

Теперь, даже если я заменю его на функцию в цикле, такую ​​как

class Program
{
    static void Main(string[] args)
    {
        for (int  i = 0; i < Foo(); i++)
        {
            Console.WriteLine(i);
            i++;

        }
        Console.Read();
    }

    private static int Foo()
    {
        return 20;
    }

Я получаю этот код IL

    .method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 2
    .locals init (
        [0] int32 i,
        [1] bool CS$4$0000)
    L_0000: nop 
    L_0001: ldc.i4.0 
    L_0002: stloc.0 
    L_0003: br.s L_0016
    L_0005: nop 
    L_0006: ldloc.0 
    L_0007: call void [mscorlib]System.Console::WriteLine(int32)
    L_000c: nop 
    L_000d: ldloc.0 
    L_000e: ldc.i4.1 
    L_000f: add 
    L_0010: stloc.0 
    L_0011: nop 
    L_0012: ldloc.0 
    L_0013: ldc.i4.1 
    L_0014: add 
    L_0015: stloc.0 
    L_0016: ldloc.0 
    L_0017: call int32 TestBedForums.Program::Foo()
    L_001c: clt 
    L_001e: stloc.1 
    L_001f: ldloc.1 
    L_0020: brtrue.s L_0005
    L_0022: call int32 [mscorlib]System.Console::Read()
    L_0027: pop 
    L_0028: ret 
}

, который выглядит так же для меня.

Так что мне кажется, что нет никакой разницы в FOR LOOP с конечным пределом, другой с пределом вычисления и последним с пределом, полученным из функции.

Итак, пока код копируется, вы знаете, что делаете в гигантском цикле, подобном этому, и у вас достаточно памяти в процессе, я думаю, что он будет работать и производить тот же результат. (в C #)

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

Да, значение сравнения рассчитывается для каждого цикла цикла.

Если вам абсолютно необходимо использовать цикл for, который редко действительно необходим, на самом деле существует только один «хороший» шаблон цикла:

for (int i = first(), last = last(); i != last; ++i)
{
// body
}

Обратите внимание также на приращение префикса.

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

Это выглядит, чтобы рассчитываться каждый раз. Разборка с VS2008.

0000003b  nop              
            for (Int64 i = 0; i < (Int64)12332 * (Int64)324234; i++)
0000003c  mov         qword ptr [rsp+20h],0 
00000045  jmp         000000000000005E 
            {
00000047  nop              
                bool h = false;
00000048  mov         byte ptr [rsp+28h],0 
            }
0000004d  nop              
            for (Int64 i = 0; i < (Int64)12332 * (Int64)324234; i++)
0000004e  mov         rax,qword ptr [rsp+20h] 
00000053  add         rax,1 
00000059  mov         qword ptr [rsp+20h],rax 
0000005e  xor         ecx,ecx 
00000060  mov         eax,0EE538FB8h 
00000065  cmp         qword ptr [rsp+20h],rax 
0000006a  setl        cl   
0000006d  mov         dword ptr [rsp+2Ch],ecx 
00000071  movzx       eax,byte ptr [rsp+2Ch] 
00000076  mov         byte ptr [rsp+29h],al 
0000007a  movzx       eax,byte ptr [rsp+29h] 
0000007f  test        eax,eax 
00000081  jne         0000000000000047 
0 голосов
/ 31 июля 2010

Как заметил @Chaos, он не скомпилируется. Но если вы используете представимое выражение (например, 100 * 100), результат, вероятно, будет жестко закодирован. На Mono CIL включает в себя:

IL_0007:  ldloc.0
IL_0008:  ldc.i4.1
IL_0009:  add
IL_000a:  stloc.0
IL_000b:  ldloc.0
IL_000c:  ldc.i4 10000
IL_0011:  blt IL_0007

Как видите, 100 * 100 жестко закодировано как 10000. Однако, как правило, оно будет оцениваться каждый раз, и если вызывается метод или свойство, его, вероятно, нельзя оптимизировать.

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