原文: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>實例,並求和:
- 用for-loop迭代:for (int i = 0; i < List<int>.Count; ++i)
- 用for-loop,但是不調用Count:for (int i = 0; i < NUM_ITEMS; ++i)
- 用foreach-loop:foreach (int i in List<T>)
- 用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調用?)。