foreach vs List .Foreach


原文:http://diditwith.net/2006/10/05/PerformanceOfForeachVsListForEach.aspx

今天當我用foreach循環迭代一個List<int>時,我發現我變得更加了解性能問題,而以前我會去迭代一個int的ArrayList,我對此感到一點沾沾自喜。得益於泛型所帶來的好處,C#編譯器可以用System.Collections.Generics.IEnumerator<int>避免大量的裝箱(boxing)操作,相比使用老式的System.Collections.IEnumerator。我開始想:這真的是最快的方式嗎?在經過一番調查研究后,這(foreach)還不是最快的方式。

.NET 2.0發布了一些小的nuggets,可以使得我們更容易地編寫代碼。我最喜歡的當屬Array和List<T>說添加的額外方法,這些方法接受Action<T>,Converter<TInput, TOutput>和Predicate<T>作為泛型delegate。事實上,我對這些東西還是很着迷的。當這些方法與匿名方法和lambda表達式結合使用時將發揮巨大作用。

我感興趣的一個特別的方法是List<T>.ForEach(Action<T>)。我很好奇,ForEach調用比一個標准的foreach-loop要來的快還是慢,還是一樣快?考慮如下代碼:

 

C#編譯器會為上面的代碼生成類似如下的偽代碼:

 

 

事實上,C#編譯器為每次迭代生成了2個方法調用:IEnumerator<T>.MoveNext()和IEnumerator<T>.Current。List<T>.Enumerator結構(用IEnumerator實現)允許編譯器生成 call IL指令而不是 callvirt ,這會有一點性能提升。相反,考慮如下代碼:

 

 

或者,等價的lambda表達式:

 

 

使用List<T>.ForEach只會導致每次迭代中使用1個方法調用:無論你提供了什么樣的Action<T>委托。這會被用callvirt IL指令調用,但是2個 call指令(一個MoveNext和一個Current)應該比一個 callvirt指令慢。所以我期望是List<T>.ForEach會更快一個(比foreach)。

 

有了這些假設,我創建了一個小的console程序,用以下4中不同的方法來迭代一個List<int>實例,並求和:

 

  1. 用for-loop迭代:for (int i = 0; i < List<int>.Count; ++i)
  2. 用for-loop,但是不調用Count:for (int i = 0; i < NUM_ITEMS; ++i)
  3. 用foreach-loop:foreach (int i in List<T>)
  4. 用List<int>.ForEach來迭代:List<int>.ForEach(delegate(int i) { result += i; })

 

首先,測試沒有開啟優化情況:


這些結果令人難以想象。結果,當不開啟編譯器優化,List<int>.ForEach比一個for-loop還要快!foreach和for-loop幾乎同樣快。所以如果你在沒有開啟優化的情況下編譯你的程序,List<int>.ForEach是最快的方式。

接下來,我開啟編譯器優化來獲得一個比較真實的結果:


看這些數字,編譯器對for-loop優化超過50%而印象深刻。foreach-loop同樣也獲得了大約20%的提升。List<int>.ForEach沒有獲得很多的優化,但是需要注意,ForEach依然比foreach-loop來的快很多。List<T>.ForEach比標准的foreach-loop來的快

注意,這些測試在一台Dell Inspiron 9400的筆記本上運行,CPU是Core Duo T2400,2GB內存。如果你想要自己測試一些結果,你可以下載ForEachTest.zip(3.82KB)

一幅圖片勝過千言萬語,我生成了一個圖表來展示不同迭代之間的速度差異。圖表中顯示了5個不同的取樣,分別從10,000,000到50,000,000次迭代。

未啟編譯器優化:


開啟編譯器優化:


最終觀點:雖然ForEach方法在迭代List<T>是非常快,但是碰到數組時就不一樣了。一維數組沒有ForEach方法,而且用ForEach比用foreach來的慢很多。原因是編譯器沒有為foreach 迭代數組生成IEnumerator<T>代碼。用foreach來迭代數組,不會有方法調用,但是Array.ForEach還是會為每次迭代調用一次委托(一個callvirt調用?)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM