2009-06-20 157 views
5

我想弄清楚for循环是否比foreach循环更快,并使用System.Diagnostics类来定时执行任务。在运行测试时,我发现首先总是执行的循环比最后一个慢。有人可以告诉我为什么会发生这种情况吗?我的代码如下:为什么第二个for循环总是比第一个执行得更快?

using System; 
using System.Diagnostics; 

namespace cool { 
    class Program { 
     static void Main(string[] args) { 
      int[] x = new int[] { 3, 6, 9, 12 }; 
      int[] y = new int[] { 3, 6, 9, 12 }; 

      DateTime startTime = DateTime.Now; 
      for (int i = 0; i < 4; i++) { 
       Console.WriteLine(x[i]); 
      } 
      TimeSpan elapsedTime = DateTime.Now - startTime; 

      DateTime startTime2 = DateTime.Now; 
      foreach (var item in y) { 
       Console.WriteLine(item); 
      } 
      TimeSpan elapsedTime2 = DateTime.Now - startTime2; 

      Console.WriteLine("\nSummary"); 
      Console.WriteLine("--------------------------\n"); 
      Console.WriteLine("for:\t{0}\nforeach:\t{1}", elapsedTime, elapsedTime2); 

      Console.ReadKey(); 
     } 
    } 
} 

这里是输出:

for:   00:00:00.0175781 
foreach:  00:00:00.0009766 
+5

只是一个快速注:当你计时的东西来确定相对执行时间,不输出任何东西(WriteLine())。执行WriteLine()所花费的时间可能比您要测试的时间长数千倍(至数百万倍),因此您失去了所有准确性。此外,你需要超过四(4)次迭代才有意义。尝试成千上万(甚至数百万)。 – 2009-06-20 14:53:36

+4

您的问题只有一个真正的答案:您的基准严重瑕疵。人们提到的其他观点是真实的,但这不是你得到这个结果的原因。 – 2009-06-20 15:01:09

+0

如果您先执行foreach循环,它会说什么? – 2009-06-20 16:09:50

回答

15

大概是因为类(例如控制台)必须是JIT编译过的第一次。你可以通过调用所有方法来获得最好的度量标准(先将它们(然后温暖)),然后执行测试。

正如其他用户所指出的,4次通过永远不会足以向您显示不同之处。

顺便说一句,for和foreach之间的性能差异可以忽略不计,使用foreach的可读性好处几乎总是超过任何边际性能好处。

2

之所以是有在foreach版本几种形式的开销是不存在的for循环

  • 使用IDisposable。
  • 一个额外的方法调用每个元素。必须使用IEnumerator<T>.Current这是一个方法调用,才能在引擎盖下访问每个元素。因为它在一个界面上,所以不能内联。这意味着N方法调用,其中N是枚举中元素的数量。 for循环只是使用和索引器
  • 在foreach循环中,所有调用都通过一个接口。一般来说,这一点不是通过一个具体类型

较慢请注意我上面列出的东西一定巨额费用。它们的成本通常很小,可能会导致性能差异很小。

另请注意,正如Mehrdad指出的那样,编译器和JIT可能会选择为某些已知数据结构(如数组)优化foreach循环。最终结果可能只是一个for循环。

注意:您的性能基准通常需要更多的工作才能保证准确。

  • 您应该使用StopWatch而不是DateTime。性能基准测试更准确。
  • 您应该多次执行测试不止一次
  • 您需要在每个循环上执行虚拟运行以消除第一次使用JITing方法时出现的问题。这可能不是一个问题,当所有的代码是在相同的方法,但它并没有受到伤害。
  • 您需要在列表中使用多于4个值。尝试40,000代替。
+0

对不起,我不清楚。如果我把foreach循环放在第二位,那么它会比for循环执行得更快。 – 2009-06-20 14:42:01

7
  1. 我不会使用日期时间来衡量性能 - 尝试Stopwatch类。
  2. 只有4次通过测量永远不会给你一个好的结果。更好地使用> 100.000遍(可以使用外部循环)。不要在你的循环中做Console.WriteLine
  3. 更妙的是:使用一个分析器(如蚂蚁展鹏也许NProf)
1

你应该使用StopWatch来定时行为。

从技术上讲,循环更快。 Foreach在IEnumerable的迭代器上调用MoveNext()方法(创建方法堆栈和其他开销),当对于只需增加一个变量。

3

我不是那么喜欢C#,但是当我没有记错的时候,微软正在为Java构建“Just in Time”编译器。当他们在C#中使用相同或相似的技术时,“某些结构第二次执行得更快”将是非常自然的。

例如,它可能是JIT系统看到一个循环被执行并决定adhoc编译整个方法。因此,当到达第二个循环时,它仍然被编译并且执行速度比第一个更快。但这是我的一个相当简单的猜测。当然,您需要对C#运行时系统有更深入的了解,以了解发生了什么。也可能是,RAM-Page在第一个循环中首先被访问,而第二个仍然在CPU高速缓存中。

Addon:另一个评论:在第一次循环中第一次输出模块可能会比第一次更接近我。现代语言是非常复杂的,可以找出底下做了什么。此外,我的这个陈述适合于这个猜测:

但是你也有你的循环终端输出。他们让事情变得更加困难。也可能是,在程序中第一次打开终端需要花费一些时间。

3

我只是在进行测试以获取一些实数,但与此同时,Gaz殴打我以答复 - 第一次调用Console.Writeline时调用Jitted,因此您在第一次循环中支付这笔费用。

只是为了虽然信息 - 用秒表而不是datetime和测量的刻度数:第一个循环之前

没有到Console.Writeline通话的时间是

 
for: 16802 
foreach: 2282 

与呼叫到Console.Writeline他们

 
for: 2729 
foreach: 2268 

虽然这些结果是由于运行的数量有限,不能持续重复,而T3他的差距总是大致相同。


参考编辑的代码:

 int[] x = new int[] { 3, 6, 9, 12 }; 
     int[] y = new int[] { 3, 6, 9, 12 }; 

     Console.WriteLine("Hello World"); 

     Stopwatch sw = new Stopwatch(); 

     sw.Start(); 
     for (int i = 0; i < 4; i++) 
     { 
      Console.WriteLine(x[i]); 
     } 
     sw.Stop(); 
     long elapsedTime = sw.ElapsedTicks; 

     sw.Reset(); 
     sw.Start(); 
     foreach (var item in y) 
     { 
      Console.WriteLine(item); 
     } 
     sw.Stop(); 
     long elapsedTime2 = sw.ElapsedTicks; 

     Console.WriteLine("\nSummary"); 
     Console.WriteLine("--------------------------\n"); 
     Console.WriteLine("for:\t{0}\nforeach:\t{1}", elapsedTime, elapsedTime2); 

     Console.ReadKey(); 
1

我不明白为什么大家在这里说,for是在这种特殊情况下超过foreach更快。对于List<T>,它是(通过列表比foreach慢约两倍到比for通过List<T>慢两倍)。

其实foreach会比这里的for略快。因为foreach阵列上基本上编译为:

for(int i = 0; i < array.Length; i++) { } 

使用.Length作为停止条件允许JIT对数组访问除去边界检查,因为它是一种特殊情况。使用i < 4使JIT插入额外的指令来检查每次迭代i是否超出数组的范围,并且在这种情况下抛出异常。但是,使用.Length时,它可以保证您永远不会超出数组边界,因此边界检查是多余的,使其更快。

但是,在大多数循环中,与内部完成的工作相比,循环的开销是微不足道的。

您看到的差异只能通过我猜测的JIT来解释。

1

我不会读太多 - 这是不好的分析代码,原因如下:
1. DateTime不适用于分析。您应该使用QueryPerformanceCounter或StopWatch,它们使用CPU硬件配置文件计数器
2. Console.WriteLine是一种设备方法,因此可能会有微妙的影响,如缓冲需要考虑
3.运行每个代码块的一次迭代将永远不会给你准确的结果,因为你的CPU在飞行优化,如乱序执行和指令调度等方面做了很多时髦
4.两个代码块获得JITed的代码很可能是相当相似的,所以很可能在指令缓存为第二代码块

为了更好地了解计时,我做了以下操作

  1. 与数学表达式替换Console.WriteLine命令(E^NUM)
  2. 我用QueryPerformanceCounter的/ QueryPerformanceTimer通过P /调用
  3. 我跑的每个代码块,然后100万次平均的结果

当我这样做,我得到了以下结果:

for循环了0.000676毫秒
foreach循环了0.000653毫秒

所以的foreach是非常稍快,但不是很多

然后我做了一些进一步的试验和第一次运行的foreach块和块第二
当我这样做,我得到了以下结果:

foreach循环花了0.000702毫秒
for循环花费了0.000691毫秒

最后我将两个循环一起运行两次i。E对于+的foreach然后+的foreach再次
当我这样做,我得到了以下结果:

foreach循环了0.00140毫秒
for循环了0.001385毫秒

所以基本上它看起来对我来说,无论你第二次运行的代码是什么,运行速度都会稍微快一些,但不足以具有任何意义。
- 编辑 -
这里有几个有用的链接
How to time managed code using QueryPerformanceCounter
The instruction cache
Out of order execution

相关问题