Подтвердить что ты не робот

Являются ли пределы для циклов, рассчитанных один раз или с каждым циклом?

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

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

Ответ 1

Для этого он рассчитывается один раз или, более вероятно, 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() не изменится во время цикла.

Ответ 2

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

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

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

Итак, например:

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

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

Ответ 3

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

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

}

Ответ 4

Есть два способа интерпретировать ваш вопрос:

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

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

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

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

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

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

Ответ 5

Прежде всего цикл 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 с конечным пределом, другой с лимитом вычисления и последним с пределом, исходящим от функции.

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

Ответ 6

Как отметил @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. Однако, как правило, он будет оцениваться каждый раз, и если вызывается метод или свойство, это, вероятно, не может быть оптимизировано.

Ответ 7

Кажется, что он рассчитывается каждый раз. Разборка с 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 

Ответ 8

В дополнение к ответу KLee1 я пишу абсолютно ту же вещь, немного отличающуюся от нее, чтобы сделать ее более читабельной:

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

someFunction() все еще выполняется только один раз, и головка цикла все еще удобно помещается в 1 строку, не слишком запутывая ваших товарищей по команде. С надеждой! ;-)

Кстати, это работает и в C++.

Ответ 9

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

Если вам абсолютно необходимо использовать for-loop, что редко нужно больше, afaik существует только один "хороший" шаблон цикла:

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

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