8

运行一个具有大量迭代的空 for 循环,运行所需的时间我得到了截然不同的数字:

public static class Program
{
    static void Main()
    {
        var sw = new Stopwatch();
        sw.Start();
        for (var i = 0; i < 1000000000; ++i)
        {
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds);
    }
}

以上将在我的机器上运行大约 200 毫秒,但如果我将其增加到 1000000001,则需要4 倍的时间!然后如果我把它设为 1000000002,那么它又会下降到 200 毫秒!

似乎发生在偶数次迭代中。如果我去for (var i = 1; i < 1000000001,(注意从 1 而不是 0 开始)然后是 200 毫秒。或者如果我这样做i <= 1000000001(注意小于或等于),那么它是 200 毫秒。或者(var i = 0; i < 2000000000; i += 2)也一样。

这似乎只在 x64 上,但在所有 .NET 版本上(至少)4.0。此外,它仅在调试器分离的发布模式下出现。

更新我认为这可能是由于 jit 中的一些巧妙的位移,但以下似乎反驳了这一点:如果您在该循环中执行诸如创建对象之类的操作,那么这也需要大约 4 倍的时间:

public static class Program
{
    static void Main()
    {
        var sw = new Stopwatch();
        sw.Start();
        object o = null;
        for (var i = 0; i < 1000000000; i++)
        {
            o = new object();
        }
        sw.Stop();
        Console.WriteLine(o); // use o so the compiler won't optimize it out
        Console.WriteLine(sw.ElapsedMilliseconds);
    }
}

这在我的机器上大约需要 1 秒,但随后增加 1 到 1000000001 需要4 秒。那是额外的 3000 毫秒,所以它不可能真的是由于位移,因为这也会显示为原始问题的 3000 毫秒差异。

4

1 回答 1

6

那么这里是反汇编:

00000031  xor         eax,eax 
  for (var i = 0; i < 1000000001; ++i)
00000033  inc         eax           
00000035  cmp         eax,3B9ACA01h 
0000003a  jl          0000000000000033 
0000003c  movzx       eax,byte ptr [rbx+18h] 
00000040  test        eax,eax 
00000042  je          0000000000000073 

00000031  xor         eax,eax 
     for (var i = 0; i < 1000000000; ++i)
00000033  add         eax,4 
00000036  cmp         eax,3B9ACA00h 
0000003b  jl          0000000000000033 
0000003d  movzx       eax,byte ptr [rbx+18h] 
00000041  test        eax,eax 
00000043  je          0000000000000074 

我看到的唯一区别是,在偶数循环中,循环索引一次增加 4(add eax 4)而不是一次增加 1(inc eax),因此它以 4 倍的速度完成循环。

这只是推测,但我相信它会将循环展开4 倍。因此它将主体放置在循环内 4 次,并且仅以 4 倍的速度递增。但是因为主体是空的,所以空主体乘以 4 仍然是空的,因此您获得的收益比循环展开所期望的要大得多。

于 2013-08-10T21:40:32.060 回答