您的位置:首页 > 其它

由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:

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: