.NET x64中的for循环性能奇怪:偶数迭代亲和力?

运行一个包含大量迭代的空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); } } 

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

似乎发生在偶数次迭代中。 如果我去for (var i = 1; i < 1000000001 ,(注意从1开始而不是0)那么它是200ms。或者如果我做i <= 1000000001 (注意小于或等于 )那么它是200ms。或者(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毫秒的差异。

那么这里是拆卸:

 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 

我看到的唯一区别是,在偶数循环中,循环索引一次add eax 4add eax 4 )而不是一次add eax 4 1( inc eax ),因此它完成循环4x更快。

这只是推测,但我相信它将循环展开了 4倍。因此它将主体放置在循环中4次,并且增加4倍。 但是因为身体是空的,空体时间4仍然是空的,你获得比环路展开所期望的更大的增益。