关于非常大的循环的后果的问题

时间:2010-09-30 21:46:07

标签: c# loops

当循环使用非常大的语言(例如在C#1.0中)时,例如,5,000,000次循环迭代,可能会导致什么问题(性能/ GC等)。这仅适用于循环体。

5 个答案:

答案 0 :(得分:3)

以下方法无法执行5,000,000次:

public static void TestTest()
{            
  for(int i = 0; i != 5000000; ++i);
}

编译产生以下IL:

.method public hidebysig static void TestTest() cil managed
{
    .maxstack 2
    .locals init (
        [0] int32 num)
    L_0000: ldc.i4.0 
    L_0001: stloc.0 
    L_0002: br.s L_0008
    L_0004: ldloc.0 
    L_0005: ldc.i4.1 
    L_0006: add 
    L_0007: stloc.0 
    L_0008: ldloc.0 
    L_0009: ldc.i4 0x4c4b40
    L_000e: bne.un.s L_0004
    L_0010: ret 
}

在Pentium上运行,我发现它被JITted:

00000000  push        ebp  
00000001  mov         ebp,esp 
00000003  push        eax  
00000004  cmp         dword ptr ds:[002030E4h],0 
0000000b  je          00000012 
0000000d  call        6CE7A839 
00000012  xor         edx,edx 
00000014  mov         dword ptr [ebp-4],edx 
00000017  xor         edx,edx 
00000019  mov         dword ptr [ebp-4],edx 
0000001c  nop              
0000001d  jmp         00000022 
0000001f  inc         dword ptr [ebp-4] 
00000022  cmp         dword ptr [ebp-4],4C4B40h 
00000029  jne         0000001F 
0000002b  nop              
0000002c  mov         esp,ebp 
0000002e  pop         ebp  
0000002f  ret

大多数情况下,这只是花费时间递增值,将其与5000000进行比较,然后如果不相等则跳回几个字节。很可能所有这些都将在L1缓存中。如果使用手工编码的程序集,它可能比这更有效,尽管当然应该优化它的真正方法是忽略整个事情,但是除非你用任何语言都能达到这个效果。

总而言之,它既不是语言的大小,也不是循环本身。

答案 1 :(得分:2)

正如大家所说的那样,循环体中发生的事情是重要的部分(如果这是你的问题,请发布代码/ pseduo代码)。

我编写了应用程序(主要是测试驱动程序),它们在遍历数百万次的循环中分配,使用和处理对象。我相信我使用的最大循环至少有1500万个循环。

机器的结束状态与启动状态相同。 “良好的表现”是主观的,但我对结果很满意。

FWIW,有些进程在Windows中无限循环,证明在许多情况下迭代次数可以被认为是无关紧要的。这是循环中发生的事情,但是现代的内存分配/处置例程(无论是手动还是GC自动化)都能够跟上一个合理的循环。

当然,您可以编写一个繁忙的循环来故意减损系统的性能,例如向非托管对象打开数百万个句柄,而不会在.Net中丢弃这些句柄。

编辑:在看到Jon的IL /汇编之后,似乎IL编译器没有优化掉循环,即使它是空的,所以我已经删除了这个理论。

答案 2 :(得分:0)

GC在您的应用程序之外运行,当您达到阈值时,它会自动清理它的内容。只要GC认为合适,任何在循环体范围内定义但未保持在较低范围内的东西都将被标记为最终确定/收集。

除此之外,一个大循环(500万)没有真正的性能问题(至少没有更多的信息)。它只会迭代并完成它的工作。

答案 3 :(得分:0)

在一个大循环中,尽量避免分配比你需要的更多的对象,否则垃圾收集器会反复跳转并暂停循环。通常情况下,当应用程序等待其他内容时,垃圾收集器可以跳入并工作,因此它甚至都不会显而易见,但是在长循环中,垃圾收集器没有空闲时间可以利用。

如果要在循环中访问大型数组,请尝试以线性方式访问它。这样,您使用的数据大多数时间都在内存缓存中。例如,如果您循环遍历二维数组并将第一个索引作为内部循环,则您将在数组中来回跳转,从而导致更多缓存未命中。

当然,使用+=在循环中创建字符串会非常糟糕,因为每次迭代都会对更大和更大的字符串进行洗牌。例如,通过一次添加一个字符来创建10000个字符的字符串在我的计算机上需要40毫秒,但创建一个100000字符的字符串不需要400毫秒,而是8000毫秒。

答案 4 :(得分:0)

除了发布的其他答案之外,您应该避免在GUI应用程序的UI线程/主线程中放置一个长时间运行的循环。如果您正在编写控制台应用程序,这无关紧要,但如果您正在编写WinForms或WPF应用程序,则会发生这种情况。

Windows应用程序的UI响应性是由消息处理驱动的,如果你进入一个长时间运行的循环而没有“即将播出”处理窗口消息,你的用户界面将会冻结,你的用户会认为你的应用已经崩溃了。

您可以将消息处理添加到长时间运行循环的主体(Delphi / C ++ VCL中的Application.ProcessMessages),但这会导致重入的新问题 - 用户在for循环中选择菜单项会导致递归。

对于GUI应用程序,最好的选择是在后台线程中放置这样的长时间循环,以便您的UI保持响应。