foreach vs List<T>.Foreach

今天当我用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要来的快还是慢,还是一样快?考虑如下代码:

[csharp] view plaincopyprint?

  1. long Sum(List<int> intList)
  2. {
  3. long result = 0;
  4. foreach (int i in intList)
  5. result += i;
  6. return result;
  7. }

C#编译器会为上面的代码生成类似如下的伪代码:

[csharp] view plaincopyprint?

  1. long Sum(List<int> intList)
  2. {
  3. long result = 0;
  4. List<T>.Enumerator enumerator = intList.GetEnumerator();
  5. try
  6. {
  7. while (enumerator.MoveNext())
  8. {
  9. int i = enumerator.Current;
  10. result += i;
  11. }
  12. }
  13. finally
  14. {
  15. enumerator.Dispose();
  16. }
  17. return result;
  18. }

事实上,C#编译器为每次迭代生成了2个方法调用:IEnumerator<T>.MoveNext()和IEnumerator<T>.Current。List<T>.Enumerator结构(用IEnumerator实现)允许编译器生成call IL指令而不是callvirt ,这会有一点性能提升。相反,考虑如下代码:

[csharp] view plaincopyprint?

  1. long Sum(List<int> intList)
  2. {
  3. long result = 0;
  4. intList.ForEach(delegate(int i) { result += i; });
  5. result result;
  6. }

或者,等价的lambda表达式:

[csharp] view plaincopyprint?

  1. long Sum(List<int> intList)
  2. {
  3. long result = 0;
  4. intList.ForEach(i => result += i);
  5. return result;
  6. }

使用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调用?)。

时间: 2024-10-12 02:34:25

foreach vs List<T>.Foreach的相关文章

js五种不同的遍历 (filter, map,foreach,every, some,)

var arr=[1,2,"a",2,4,1,4,"a",5,6,7,8,"aa","bb","c","a"] // foreach console.log(arr.forEach((item) => { return item === "a" // undefined. foreach无返回值,只是遍历执行 })) // map console.log(arr

说说PHP中foreach引用的一个坑

From: http://blog.csdn.net/yipiankongbai/article/details/45307767 先来看看下面这段代码: <?php $arr = array('apple','banana','cat','dog'); foreach($arr as $key=>$val) { //some code } echo $val; //输出dog echo $key; //输出3 //下面对val进行赋值 $val = 'e'; print_r($arr); /

【JS】&lt;c:foreach&gt;用法

<c:foreach>类似于for和foreach循环   以下是我目前见过的用法: 1.循环遍历,输出所有的元素. 1 <c:foreach items="${list}" var="li"> 2 ${li} 3 </c:foreach> 注意:items 用于接收集合对象,var 定义对象接收从集合里遍历出的每一个元素.同时其会自动转型. 2.循环遍历,输出一个范围类的元素. 1 <c:foreach items =&q

c#--foreach遍历的用法与split的用法

一. foreach循环用于列举出集合中所有的元素,foreach语句中的表达式由关键字in隔开的两个项组成.in右边的项是集合名,in左边的项是变量名,用来存放该集合中的每个元素.该循环的运行过程如下:每一次循环时,从集合中取出一个新的元素值.放到只读变量中去,如果括号中的整个表达式返回值为true,foreach块中的语句就能够执行.一旦集合中的元素都已经被访问到,整个表达式的值为false,控制流程就转入到foreach块后面的执行语句. foreach语句经常与数组一起使用,下面实例将通

【tapestry3学习笔记】之 foreach组件

foreach组件是tapestry3 提供的组件之一,是一个很重要的组件,其作用用于遍历.其一般格式为: <span jwcid="@Foreach" souece="XXX" value="XXX"></span> jwcid:是对应的java web component id.   source:是对应的java类里的集合或者数组 需要抽象 或者提供set get方法.   value:是循环这个source对象代表

JSP 基础之 JSTL &lt;c:forEach&gt;用法

摘录自:http://www.cnblogs.com/jokerjason/p/5740917.html JSTL所支持的迭代标签有两个,分别是<c:forEach>和<c:forTokens>.这里介绍的是<c:forEach>标签. <c:forEach>标签的作用就是迭代输出标签内部的内容.它既可以进行固定次数的迭代输出,也可以依据集合中对象的个数来决定迭代的次数. <c:forEach>标签,需要与el表达式联合使用 <c:forE

JavaScript中的数组遍历forEach()与map()方法以及兼容写法

原理: 高级浏览器支持forEach方法 语法:forEach和map都支持2个参数:一个是回调函数(item,index,list)和上下文: forEach:用来遍历数组中的每一项:这个方法执行是没有返回值的,对原来数组也没有影响: 数组中有几项,那么传递进去的匿名回调函数就需要执行几次: 每一次执行匿名函数的时候,还给其传递了三个参数值:数组中的当前项item,当前项的索引index,原始数组input: 理论上这个方法是没有返回值的,仅仅是遍历数组中的每一项,不对原来数组进行修改:但是我

js数组中indexOf/filter/forEach/map/reduce详解

今天在网上看到一篇帖子,如题: 出处:前端开发博客 (http://caibaojian.com/5-array-methods.html) 在ES5中一共有9个Array方法,分别是: Array.prototype.indexOf Array.prototype.lastIndexOf Array.prototype.every Array.prototype.some Array.prototype.forEach Array.prototype.map Array.prototype.f

JavaScript中如何中断forEach循环

先来看下forEach的实现 // Production steps of ECMA-262, Edition 5, 15.4.4.18// Reference: http://es5.github.io/#x15.4.4.18if (!Array.prototype.forEach) {   Array.prototype.forEach = function(callback, thisArg) {     var T, k;     if (this === null) {      th