对于.NET x64中的循环性能异常:偶数迭代相关性
本文关键字:异常 相关性 迭代 性能 循环性 x64 NET 循环 对于 | 更新日期: 2023-09-27 18:27:15
运行一个带有大量迭代的空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,则需要4x的时间!如果我达到1000000002,那么它又降到200ms!
这种似乎发生在偶数次迭代中。如果我使用for (var i = 1; i < 1000000001
(注意从1而不是0开始),那么它是200ms。或者,如果我做i <= 1000000001
(注意小于或等于),那么它是200ms。或(var i = 0; i < 2000000000; i += 2)
。
这似乎仅适用于x64,但适用于(至少)4.0之前的所有.NET版本。此外,它仅在调试器分离的发布模式下显示。
UPDATE我认为这可能是由于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秒。这是一个额外的3000ms,所以这不可能真的是由于比特移位,因为在最初的问题中,这也会显示为3000ms的差异。
以下是反汇编:
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仍然是空的。你从循环展开中获得的增益比你预期的要大得多。