2009-06-20 154 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

相關問題