循环比for循环要快得多,然后当语句改变时,while循环更快。怎么了?

时间:2012-01-09 10:16:29

标签: c# performance

出于兴趣,我测试了for循环和while循环是否有任何不同做同样的事情。是什么原因导致while循环在我的计算机(AMD Phenom II X6 1090T @ 3.20GHz)上比for循环长约2-2.5秒?他们不是在做同样的事情吗?你得到类似的结果吗?

此外,当我使用空语句替换循环中的x = null;语句时,while循环将明显更快。这是怎么回事?

当然,迭代次数非常多,但差异是不是仍然相当显着?

static void Main(string[] args)
{
    String x;
    const Int64 FIVE_BN = 5000000000;
    Int64 i = 0;

    DateTime start = DateTime.Now;
    for (; FIVE_BN > i; i++)
        x = null; //Replace with only ; in both loops and the for loop is faster
    Console.Out.WriteLine(FIVE_BN.ToString() + " times (for): " + (DateTime.Now - start));

    i = 0;

    start = DateTime.Now;
    while(FIVE_BN > i++)
        x = null; //Replace with only ; in both loops and the for loop is faster
    Console.Out.WriteLine(FIVE_BN.ToString() + " times (while): " + (DateTime.Now - start));

    Console.Read();
    return;
}

3 个答案:

答案 0 :(得分:11)

虽然这完全是微观优化,但永远不会成为性能瓶颈。有趣的是,这两者实际上是不同的,有趣的是当你用VS2010提取循环时,我得到以下结果:

private static String forLoop(ref Int64 i)
{
    String x;

    for (; FIVE_BN > i; i++)
        x = null; //Replace with only ; in both loops and the for loop is faster
    return x;
}

private static void whileloop(ref String x, ref Int64 i)
{
    while (FIVE_BN > i++)
        x = null; //Replace with only ; in both loops and the for loop is faster
}

这很有趣......它表明这两个功能确实不同。

现在当我们用;替换循环中的逻辑时,我们得到以下提取的方法:

private static Int64 forLoopShort(Int64 i)
{

    for (; FIVE_BN > i; i++)
        ; //Replace with only ; in both loops and the for loop is faster
    return i;
}

private static Int64 whileLoopShort(Int64 i)
{

    while (FIVE_BN > i++)
        ; //Replace with only ; in both loops and the for loop is faster
    return i;
}

这表明循环与此配置基本相同的原因。

要想弄清楚它们在内联时是如何不同的(而不是提取到方法中),我们需要看看优化的CLR编码是什么样的(虽然优化器可能实际上删除了两个函数之间的任何显着差异)。这是某种东西以后的编辑。

编辑:

CIL揭示了差异:

For循环有.maxstack 2但是while循环有.maxstack 4,否则操作顺序会有一点不同,因为while的增量发生在循环开始但for操作发生在循环结束时(将循环内容更改为Console.WriteLine(i)并看到While循环将从1打印但For循环将从0打印(尽管两者都进行了相同数量的循环迭代)。

当循环内容只是;时,两个循环在CIL中缩短了2行,删除了以下行(对于两个循环):

IL_0006:  ldnull
IL_0007:  stloc.0

然而,当我们构建版本时代码非常不同:

x = null;;之间的区别对于任何一个循环都没有,因为优化器已经注意到值永远不会变为非null。

优化的for和while循环之间的区别如下:

CIL for循环:

IL_0000:  ldc.i4.0
IL_0001:  conv.i8
IL_0002:  stloc.0
IL_0003:  br.s       IL_000a
IL_0005:  ldloc.0
IL_0006:  ldc.i4.1
IL_0007:  conv.i8
IL_0008:  add
IL_0009:  stloc.0
IL_000a:  ldc.i8     0x12a05f200
IL_0013:  ldloc.0
IL_0014:  bgt.s      IL_0005
IL_0016:  ret

和CIL while循环:

IL_0000:  ldc.i4.0
IL_0001:  conv.i8
IL_0002:  stloc.0
IL_0003:  ldc.i8     0x12a05f200
IL_000c:  ldloc.0
IL_000d:  dup
IL_000e:  ldc.i4.1
IL_000f:  conv.i8
IL_0010:  add
IL_0011:  stloc.0
IL_0012:  bgt.s      IL_0003
IL_0014:  ret

因此我们可以看到优化的while循环比2循环的for循环更快,但它使用更多的堆栈空间。

这两者之间的差异似乎与i++出现的差异完全相关。

确实通过制作新方法证实了这一点:

private static void forLoopVeryShort()
{
    string x;
    Int64 i = 0;

    for (; FIVE_BN > i++;)
        ; //Replace with only ; in both loops and the for loop is faster
}

构建时(在发布或调试中)此for方法的CIL代码与while循环的CIL代码相同。

存在着你的不同之处。 For循环执行与完全相同的行为时,while循环执行完全相同。您注意到的差异完全是由于在调试中运行代码而不是发布,结合JIT并不总是像发布代码优化器那样高效。

我很喜欢这个问题,我从中学到了一些东西;我希望其他人也这样做。 1

答案 1 :(得分:8)

你可能想要打破cordbg(并小心地启用所有JIT优化)来查看生成的本机代码,以确定为什么会发生这种情况......但为什么要这么麻烦?在真正的代码中,差异不会很大,因为你将在循环中做真正的工作。

IMO,微观优化完全不切实际的代码并不是一项富有成效的工作。即使微观优化实际代码通常通常也没有成效,除非你已经证实这是瓶颈。

答案 2 :(得分:3)

有关信息 - 无法复制:

分配:

5000000000 times (for): 00:00:15.0488608
5000000000 times (while): 00:00:12.7107270

只有一个;

5000000000 times (for): 00:00:15.0558611
5000000000 times (while): 00:00:12.7297281

(这里,我在发布模式下运行,在调试器之外等)

可能这是特定于框架的(我使用4.0.30319.488,x64)或CPU特定的(我使用的是Intel i7 4x2.67GHz(加HT)),但我的第一个猜测是如何正在运行测试。