Является ли предел в следующем цикле (12332 * 324234) рассчитанным один раз или каждый раз, когда цикл работает?
for(int i=0; i<12332*324234;i++)
{
//Do something!
}
Является ли предел в следующем цикле (12332 * 324234) рассчитанным один раз или каждый раз, когда цикл работает?
for(int i=0; i<12332*324234;i++)
{
//Do something!
}
Для этого он рассчитывается один раз или, более вероятно, 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()
не изменится во время цикла.
Это одно из наиболее часто недопонятых действий циклов в С#.
Вот что вам нужно знать:
Расчет границ цикла, если непостоянным и включающим переменную, свойство-доступ, вызов функции или вызов делегата будет повторно вычислять значение границ перед каждым итерация цикла.
Итак, например:
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()
, вероятно, будет отличаться на каждой итерации. Компилятор не пытается выполнить какой-либо статический анализ, чтобы определить, может ли выражение границ цикла быть постоянным... что было бы чрезвычайно сложно, если не невозможно. Вместо этого он предполагает, что если выражение не является константой, оно должно быть оценено на каждой итерации.
Теперь, в большинстве случаев, стоимость вычисления ограничения границ для цикла будет относительно недорогой, поэтому вам не нужно беспокоиться об этом. Но вам нужно понять, как компилятор обрабатывает такие ограничения цикла. Кроме того, в качестве разработчика вам нужно быть осторожным с использованием свойств или методов, которые имеют побочные эффекты как часть выражения границ. В конце концов, эти побочные эффекты будут возникать на каждой итерации цикла.
На самом деле это не будет компилироваться, потому что оно будет переполняться, но если вы сделаете его меньшим числом и откройте Reflector, вы найдете что-то вроде этого.
for (int i = 0; i < 0x3cf7b0; i++)
{
}
Есть два способа интерпретировать ваш вопрос:
Ответ на эти два разных вопроса:
Другими словами:
for (int i = 0; i < someString.Length; i++)
Если оценка someString.Length
является дорогостоящей, она будет налагать штраф за каждую итерацию цикла.
Прежде всего цикл 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. (в С#)
Как отметил @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. Однако, как правило, он будет оцениваться каждый раз, и если вызывается метод или свойство, это, вероятно, не может быть оптимизировано.
Кажется, что он рассчитывается каждый раз. Разборка с 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
В дополнение к ответу KLee1 я пишу абсолютно ту же вещь, немного отличающуюся от нее, чтобы сделать ее более читабельной:
for(int i=0, limit = someFunction(); i<limit ;i++)
{
//Do something!
}
someFunction()
все еще выполняется только один раз, и головка цикла все еще удобно помещается в 1 строку, не слишком запутывая ваших товарищей по команде. С надеждой! ;-)
Кстати, это работает и в C++.
Да, значение сравнения рассчитывается каждый цикл цикла.
Если вам абсолютно необходимо использовать for-loop, что редко нужно больше, afaik существует только один "хороший" шаблон цикла:
for (int i = first(), last = last(); i != last; ++i)
{
// body
}
Обратите внимание на приращение префикса.