由for V.S. for each想到的
2007-05-02 19:25
357 查看
一直想写一系列如何提高Performance和Scalability的文章,把我的相关经验和所知道的相关的技巧同大家分享。前一阵在园子里有一篇讨论for each 和 for两种循环那个具有更好的performance的blog,议论得沸沸扬扬。我觉得这是一个很好的切入点,我就已此作为引子,开始我的这个系列的文章。这篇文章的重点不是在于比较这两种循环孰优孰劣,我将讨论的重点是如何更好地定义Collection,如何在判断在什么时候该用Array,什么时候用Collection。
一、for each的本质
我们知道,所有实现了System.Collections. IEnumerable接口的类,我们都可以对它运用for each loop。为了弄清楚它的执行过程,我先给出一个Sample:
我们先运行一下上面的程序再来讲述具体的执行的流程。
struct Employee
Public virtual IEnumerator GetEnumerator()
class EmployeeEnumerator : IEnumerator
public IEnumerator GetEnumerator()
从上面的定义我们可以看到,Array的GetEnumerator并没有像我一样使用virtual的方法。实际上对于一个Array来说,确实没有必要使用virtual方法,因为你定义的具体的Array只能直接继承自System.Array,而不能继承自别的Array, 比如,你不能定义某个class继承自int[]。.在Array中实际上是使用了两个不同的Enumerator,对于一维基0数组,使用的是SZArrayEnumerator,非一维基0数组则使用的是ArrayEnumerator。我们来看看SZArrayEnumerator具体是如何定义的:
对于Current 属性,返回值仍然是object,对于一个基于Value Type的array,装箱操作在所难免。我想到现在为止,我们知道为什么for循环在performance上要优于for each的原因了吧。
分析完Array,我们来看看另一个在.NET 2.0之前经常使用的一个类:ArrayList。我们照例来看看他的GetEnumerator的定义:
同Array不同的是,GetEnumerator方法被定义成virtual,所以对于Arraylist来说,它也不可以利用Inlining的编译优化获得performance上的提升。所以我们可以得出结论,Array比ArrayList具有更好的performance。(实际上这只是Array比ArrayList具有更好的performance其中一个原因,还有一个主要的原因时Array是基于某个确定Type的数组,我们在定义的时候就已经给它指定了具体的Type)。在Arraylist中使用的Enumerator是ArrayListEnumeratorSimple。我们来看看它的定义:
Current属性返回的类型依然是object, 对于ValueType来说,装箱在所难免。不过总的来说,Array较之ArrayList在Performance具有较大的优势,对一个Performance要求比较高的Application来说,尽量使用Array来替代ArrayList。
四、优化方案
通过以上的分析,通过Enumerator遍历Collection对象来说,影响performance的主要因素体现在以下两个方面:
1. EmployeeList的GetEnumerator是virtual方法导致不能充分利用Inline的编译优化。
2. EmployeeEnumerator的Current返回值为object导致装箱。
基于这两点,我们重新定义Enumerator:OptimizedEmployeeEnumerator。
通过上面的Code,我们可以看到,我们通过显示接口实现的方式实现了object Current,并对应了另一个不需要装箱、返回类型为Employee的Current属性。
我们随之EmployeeList作相应的修改:
我们去掉我们不需要的virtual关键字,并修正GetEnumerator方法使之返回我们新定义的OptimizedEmployeeEnumerator。
五、进一步优化
通过上面对程序的修正,如果现在我们通过通过while Loop的方式来遍历EmpoyeeList,在较大数据的情况下,performance可以得到较大的提升(注:我们必须通过申明为OptimizedEmployeeEnumerator而不是Enumerator的变量来获得对应的Enumerator,这样我们才能访问Employee Current属性,而不是object Current属性,从而避免装箱,具体的原因,请查阅MSDN关于“显式接口实现“的内容)。但是使用for each来进行遍历的话,装箱还是难以避免的。我们可以通过程序来证明这一点。
我们修改Main方法:
运行程序,看看现在的输出:
class GenericEmployeeEnumerator : IEnumerator<Employee>
代码不复杂,不需要多说什么。我们对EmployeeList的GetEnumerator方法作相应的修改使之返回我们重新定义个GenericEmployeeEnumerator。为了验证,我们修改Main方法:
我们将会得到下面的输出:
六、回归简约
事实上,我们上面做的事情是在把简单的事情复杂化,我们可以利用Generic的Collection来实现相应的功能。比如我们我们通过定义System.Collections.Generic .List<Employee>来创建我们的EmploteeList对象。
相关内容:
[原创]如何改Managed Code的Performance和Scalability系列之二:深入理解string和如何高效地使用string
一、for each的本质
我们知道,所有实现了System.Collections. IEnumerable接口的类,我们都可以对它运用for each loop。为了弄清楚它的执行过程,我先给出一个Sample:
using System; using System.Collections.Generic; using System.Text; using System.Collections; namespace Artech.CustomCollection
我们先运行一下上面的程序再来讲述具体的执行的流程。
struct Employee
Public virtual IEnumerator GetEnumerator()
class EmployeeEnumerator : IEnumerator
public IEnumerator GetEnumerator()
从上面的定义我们可以看到,Array的GetEnumerator并没有像我一样使用virtual的方法。实际上对于一个Array来说,确实没有必要使用virtual方法,因为你定义的具体的Array只能直接继承自System.Array,而不能继承自别的Array, 比如,你不能定义某个class继承自int[]。.在Array中实际上是使用了两个不同的Enumerator,对于一维基0数组,使用的是SZArrayEnumerator,非一维基0数组则使用的是ArrayEnumerator。我们来看看SZArrayEnumerator具体是如何定义的:
[Serializable] private sealed class SZArrayEnumerator : IEnumerator, ICloneable
对于Current 属性,返回值仍然是object,对于一个基于Value Type的array,装箱操作在所难免。我想到现在为止,我们知道为什么for循环在performance上要优于for each的原因了吧。
分析完Array,我们来看看另一个在.NET 2.0之前经常使用的一个类:ArrayList。我们照例来看看他的GetEnumerator的定义:
public virtual IEnumerator GetEnumerator()
同Array不同的是,GetEnumerator方法被定义成virtual,所以对于Arraylist来说,它也不可以利用Inlining的编译优化获得performance上的提升。所以我们可以得出结论,Array比ArrayList具有更好的performance。(实际上这只是Array比ArrayList具有更好的performance其中一个原因,还有一个主要的原因时Array是基于某个确定Type的数组,我们在定义的时候就已经给它指定了具体的Type)。在Arraylist中使用的Enumerator是ArrayListEnumeratorSimple。我们来看看它的定义:
[Serializable] private sealed class ArrayListEnumeratorSimple : IEnumerator, ICloneable
Current属性返回的类型依然是object, 对于ValueType来说,装箱在所难免。不过总的来说,Array较之ArrayList在Performance具有较大的优势,对一个Performance要求比较高的Application来说,尽量使用Array来替代ArrayList。
四、优化方案
通过以上的分析,通过Enumerator遍历Collection对象来说,影响performance的主要因素体现在以下两个方面:
1. EmployeeList的GetEnumerator是virtual方法导致不能充分利用Inline的编译优化。
2. EmployeeEnumerator的Current返回值为object导致装箱。
基于这两点,我们重新定义Enumerator:OptimizedEmployeeEnumerator。
class OptimizedEmployeeEnumerator : IEnumerator
通过上面的Code,我们可以看到,我们通过显示接口实现的方式实现了object Current,并对应了另一个不需要装箱、返回类型为Employee的Current属性。
我们随之EmployeeList作相应的修改:
class EmployeeList : IEnumerable
我们去掉我们不需要的virtual关键字,并修正GetEnumerator方法使之返回我们新定义的OptimizedEmployeeEnumerator。
五、进一步优化
通过上面对程序的修正,如果现在我们通过通过while Loop的方式来遍历EmpoyeeList,在较大数据的情况下,performance可以得到较大的提升(注:我们必须通过申明为OptimizedEmployeeEnumerator而不是Enumerator的变量来获得对应的Enumerator,这样我们才能访问Employee Current属性,而不是object Current属性,从而避免装箱,具体的原因,请查阅MSDN关于“显式接口实现“的内容)。但是使用for each来进行遍历的话,装箱还是难以避免的。我们可以通过程序来证明这一点。
我们修改Main方法:
static void Main(string[] args)
运行程序,看看现在的输出:
class GenericEmployeeEnumerator : IEnumerator<Employee>
代码不复杂,不需要多说什么。我们对EmployeeList的GetEnumerator方法作相应的修改使之返回我们重新定义个GenericEmployeeEnumerator。为了验证,我们修改Main方法:
static void Main(string[] args)
我们将会得到下面的输出:
六、回归简约
事实上,我们上面做的事情是在把简单的事情复杂化,我们可以利用Generic的Collection来实现相应的功能。比如我们我们通过定义System.Collections.Generic .List<Employee>来创建我们的EmploteeList对象。
相关内容:
[原创]如何改Managed Code的Performance和Scalability系列之二:深入理解string和如何高效地使用string
相关文章推荐
- [原创]如何改善Managed Code的Performance和Scalability系列之一:由for V.S. for each想到的
- for-each遍历Map
- c++ STL之 for_each(权哥)
- list_for_each and kernel panic problem
- for-each@section oracle.apps.xdo.template.fo.area.PageNumber.formatString(PageNumber.java:104)
- for_each 返回值
- java基础for each
- STL: for_each
- sp_MSforeachtable sp_MSforeachdb
- Linux内核中list_head、list_for_each、list_entry、container_of之间的关系
- 【推荐】(SqlServer)不公开存储过程sp_Msforeachtable与sp_Msforeachdb详解
- from given array of n elements find the maximum element for each consecutive sub-array of k elements.
- 函数对象状态(Function Object State)获取 -- 引用传递和for_each()
- sp_MSforeachdb&sp_MSforeachtable&sp_MSforeachobject&查看某个对象的依赖对象
- 关于container_of和list_for_each_entry 及其相关函数的分析
- jquery $.each 和for怎么跳出循环终止本次循环
- AS3 中的 for each in 效率比较
- STL中的for_each调用类成员函数,mem_fun, mem_fun_ref用法
- java Iterable and for each
- for_each(c++11)