以下循环(12332 * 324234)中的限制是计算一次还是每次循环运行?
for(int i=0; i<12332*324234;i++)
{
//Do something!
}
答案 0 :(得分:29)
为此它计算一次,或者更可能是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()
的值在循环过程中不会改变。
答案 1 :(得分:14)
这是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
只是一个局部变量,访问它的成本非常低 - 在许多情况下,它将保留在本地CPU缓存中,甚至可能保存在寄存器中(取决于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()
在每次迭代时可能会有所不同。编译器不会尝试执行某些静态分析来确定循环边界表达式是否常量...即使不是不可能,也会非常复杂。相反,它假设如果表达式不是常量,则必须在每次迭代时对其进行求值。
现在,在大多数情况下,计算循环边界约束的成本相对便宜,因此您不必担心它。但是你需要了解编译器如何处理这样的循环边界。此外,作为开发人员,您需要注意使用具有副作用的属性或方法作为边界表达式的一部分 - 毕竟,这些副作用将在循环的每次迭代中发生。 / p>
答案 2 :(得分:7)
实际上它不会编译因为它会溢出但是如果你把它变成一个较小的数字并打开Reflector你会发现这样的东西。
for (int i = 0; i < 0x3cf7b0; i++)
{
}
答案 3 :(得分:3)
有两种方法可以解释您的问题:
这两个不同的问题的答案是:
换句话说:
for (int i = 0; i < someString.Length; i++)
如果对someString.Length
的评估成本很高,则每次循环迭代都会受到惩罚。
答案 4 :(得分:2)
首先,问题中的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 FOR LOOP与有限限制没有区别,另一个有计算限制,最后是来自函数的限制。
所以只要代码复制,你知道你在这样一个庞大的循环中做了什么,并且你有足够的内存,我认为它会工作并产生相同的性能。 (在C#中)
答案 5 :(得分:0)
正如@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.但是,通常每次都会对其进行求值,如果调用方法或属性,则可能无法对其进行优化。
答案 6 :(得分:0)
看起来每次都要计算。从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
答案 7 :(得分:0)
除了KLee1的答案外,我在写绝对相同的东西,只是略有不同,以使其更易于理解:
for(int i=0, limit = someFunction(); i<limit ;i++)
{
//Do something!
}
someFunction()
仍然只执行一次,循环的头部仍然可以舒适地排成一行,而不会使您的队友感到困惑。希望! ;-)
顺便说一句,这也适用于C ++。
答案 8 :(得分:-2)
是的,每个循环周期计算比较值。
如果你绝对不得不使用for循环,这几乎不再是必需的,afaik只有一个“好的”循环模板:
for (int i = first(), last = last(); i != last; ++i)
{
// body
}
注意前缀增量。