代码之家  ›  专栏  ›  技术社区  ›  GurdeepS

关于一个非常大的循环结果的问题

  •  2
  • GurdeepS  · 技术社区  · 14 年前

    当循环使用一种非常大的语言(比如说C 1.0)时,也就是说,5000000个循环迭代,这可能导致什么问题(性能/GC等)。这只适用于循环体。

    5 回复  |  直到 14 年前
        1
  •  3
  •   Jon Hanna    14 年前

    以下方法不执行5000000次:

    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上运行时,我发现它很快就会:

    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进行比较,如果不相等,则返回几个字节。很可能所有这些都将在一级缓存中。如果手工编码的程序集,它可能会比这个效率高一点,当然,优化它的真正方法是忽略整个过程,但除非这是任何语言所能达到的效果。

    总之,重要的不是语言的大小,也不是循环本身。

        2
  •  2
  •   Tim M.    14 年前

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

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

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

    在fwiw中,有一些进程在windows中无限循环,这表明迭代次数在很多情况下都是不相关的。循环内部发生的事情很重要,但是现代内存分配/处理例程(无论是手动的还是GC自动的)非常能够跟上一个合理的循环。

    当然,您可以编写一个繁忙的循环来有意降低系统的性能,例如,向非托管对象打开数百万个句柄,而从不在.NET中处理这些句柄。

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

        3
  •  0
  •   Aren    14 年前

    GC在您的应用程序之外运行,当您达到一个阈值时,它将自动清除它所能清除的内容。在循环体的范围内定义的、不在较低范围内维护的任何内容都将被标记为在GC认为合适的时候进行定稿/收集。

    除此之外,一个大循环(500万)没有实际的性能问题(至少没有更多的信息)。它将迭代并做它所做的。

        4
  •  0
  •   Guffa    14 年前

    在一个大循环中,尽量避免分配比您需要的更多的对象,否则垃圾收集器将重复地跳入并暂停循环。通常,当应用程序在等待其他东西时,垃圾收集器可以跳入并工作,因此它甚至不明显,但是在一个长循环中,垃圾收集器没有空闲时间可以利用。

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

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

        5
  •  0
  •   dthorpe    14 年前

    除了发布的其他答案之外,您还应该避免在GUI应用程序的UI线程/主线程中放置一个长时间运行的循环。如果你在写一个控制台应用程序,这并不重要,但是如果你在写一个winforms或wpf应用程序,这就很重要了。

    Windows应用程序的用户界面响应是由消息处理驱动的,如果你进入一个长时间运行的for循环,而没有“腾空”来不时地处理窗口消息,你的用户界面就会冻结,你的用户会认为你的应用程序崩溃了。

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

    对于一个GUI应用程序来说,最好的选择是在后台线程中放置这样的长时间运行的循环,这样您的UI就可以保持响应。