Hello World
Spiga

泛型真的会降低性能吗?

2009-05-29 16:41 by 老赵, 27301 visits

在《.NET,你忘记了么?(八)—— 从dynamic到特性误用》一文中,飞林沙同学提到,使用泛型会略微降低程序性能,因此在程序中使用List<Object>是不合理的行为,应该使用ArrayList。这一点和老赵平时的观点相悖,老赵一直提倡,在.NET 2.0之后,要尽可能使用List<T>,情愿是List<Object>也不要使用ArrayList。不过个中原因与性能无关,我们稍候再叙述。飞同学的文章让我有了将泛型与非泛型进行性能比较的想法。这个比较非常容易,不过也得出了一些非常有意思的结论。

泛型容器与非泛型容器的性能比较

首先,我们来比较一种最“纯粹”的泛型容器,它的目的是避免程序的其他方面对性能的影响。因此,这里我们构造两个最简单的容器,就是简单的模仿ArrayList和List<T>:

public class MyArrayList
{
    public MyArrayList(int length)
    {
        this.m_items = new object[length];
    }

    public object[] m_items;

    public object this[int index]
    {
        get
        {
            return this.m_items[index];
        }
        set
        {
            this.m_items[index] = value;
        }
    }

    public IEnumerable InnerContainer
    {
        get
        {
            return this.m_items;
        }
    }
}

public class MyList<T>
{
    public MyList(int length)
    {
        this.m_items = new T[length];
    }

    public T[] m_items;

    public T this[int index]
    {
        get
        {
            return this.m_items[index];
        }
        set
        {
            this.m_items[index] = value;
        }
    }

    public IEnumerable<T> InnerContainer
    {
        get
        {
            return this.m_items;
        }
    }
}

MyArrayList为直接使用Object类型的容器,而MyList<T>则是泛型容器。老赵为他们实现了两种操作,一是下标访问,二是直接把内部的数组容器作为可遍历的对象释放出来。这两种都是平时编程中最经常使用的操作。

于是我们就可以编写测试代码了。首先,我们初始化两种容器,并进行“预热”:

int length = 1000;
object value = new object();

MyArrayList myArrayList = new MyArrayList(length);
for (int i = 0; i < length; i++)
{
    myArrayList[i] = myArrayList[i] ?? value;
}

MyList<object> myList = new MyList<object>(length);
for (int i = 0; i < length; i++)
{
    myList[i] = myList[i] ?? value;
}

然后我们使用CodeTimer来进行统计:

CodeTimer.Initialize();
int iteration = 300 * 1000;

CodeTimer.Time("MyArrayList下标访问", iteration, () =>
{
    for (int i = 0; i < length; i++)
    {
        var o = myArrayList[i];
    }
});

CodeTimer.Time("MyList下标访问", iteration, () =>
{
    for (int i = 0; i < length; i++)
    {
        var o = myList[i];
    }
});

Release Build并运行。猜猜看,结果是什么?

MyArrayList下标访问
        Time Elapsed:   2,398ms
        CPU Cycles:     5,042,561,997
        Gen 0:          0
        Gen 1:          0
        Gen 2:          0

MyList下标访问
        Time Elapsed:   2,285ms
        CPU Cycles:     4,935,066,741
        Gen 0:          0
        Gen 1:          0
        Gen 2:          0

以上是在老赵的机器上得到的结果,从结果上看,泛型的MyList性能甚至略比MyArrayList有所提高。当然测试的结果其实是互有胜负,但是事实上,MyList的获胜的次数甚至还略有领先。

那么我们再来看看“遍历”的性能如何:

CodeTimer.Time("MyArrayList遍历", iteration, () =>
{
    foreach (object o in myArrayList.InnerContainer)
    {
        var o1 = o;
    }
});

CodeTimer.Time("MyList遍历", iteration, () =>
{
    foreach (object o in myList.InnerContainer)
    {
        var o1 = o;
    }
});

运行的结果颇有意思:

MyArrayList遍历
        Time Elapsed:   21,367ms
        CPU Cycles:     46,023,627,496
        Gen 0:          2
        Gen 1:          1
        Gen 2:          0

MyList遍历
        Time Elapsed:   3,463ms
        CPU Cycles:     7,448,928,223
        Gen 0:          2
        Gen 1:          0
        Gen 2:          0

直接使用Object的MyArrayList性能居然差了这么多!个中原因老赵有所猜测,在得到明确答案之后会接着与大家分享。

ArrayList和List<T>的性能

刚才比较了最“纯粹”的性能,那么我们再来比较ArrayList和List<T>。因为我们其实不知道它俩具体在实现上的细节是如何的,还是比较一下这两个容器具体的性能比较好。比较的内容还是两项:下标访问及遍历。代码如下:

int length = 1000;
object value = new object();

ArrayList arrayList = new ArrayList(length);
for (int i = 0; i < length; i++)
{
    arrayList.Add(value);
    arrayList[i] = arrayList[i] ?? value;
}

List<object> list = new List<object>(length);
for (int i = 0; i < length; i++)
{
    list.Add(value);
    list[i] = list[i] ?? value;
}

CodeTimer.Initialize();
int iteration = 300 * 1000;

CodeTimer.Time("ArrayList下标访问", iteration, () =>
{
    for (int i = 0; i < length; i++)
    {
        var o = arrayList[i];
    }
});

CodeTimer.Time("List下标访问", iteration, () =>
{
    for (int i = 0; i < length; i++)
    {
        var o = list[i];
    }
});

CodeTimer.Time("ArrayList遍历", iteration, () =>
{
    foreach (object o in arrayList)
    {
        var o1 = o;
    }
});

CodeTimer.Time("List遍历", iteration, () =>
{
    foreach (object o in list)
    {
        var o1 = o;
    }
});

结果如下:

ArrayList下标访问
        Time Elapsed:   2,282ms
        CPU Cycles:     4,838,476,797
        Gen 0:          0
        Gen 1:          0
        Gen 2:          0

List下标访问
        Time Elapsed:   2,302ms
        CPU Cycles:     4,979,267,920
        Gen 0:          0
        Gen 1:          0
        Gen 2:          0

ArrayList遍历
        Time Elapsed:   5,187ms
        CPU Cycles:     11,145,830,014
        Gen 0:          4
        Gen 1:          0
        Gen 2:          0

List遍历
        Time Elapsed:   2,989ms
        CPU Cycles:     6,459,825,955
        Gen 0:          0
        Gen 1:          0
        Gen 2:          0

现在您还觉得泛型会降低性能,List<Object>比ArrayList的性能差吗?

总结

从结果上已经可以看出,泛型并不会影响性能,而List<T>的性能也不比ArrayList要差。因此老赵继续坚持:在有泛型支持的情况下,尽量使用泛型容器。例如使用List<Object>而不是ArrayList。除了“性能”之外,老赵的还有其他一些理由。例如使用List<Object>的话就可以使用框架内部所定义的各种有用的辅助方法(要知道在.NET框架中,现在几乎都是在针对IEnumerable<T>进行开发);而我们平时写程序时,也可以统一的针对泛型编程,如IList<T>,IEnumerable<T>,不必考虑List或Enumerable等非泛型元素。

放心使用List<Object>吧。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

96 条回复

  1. 李永京
    *.*.*.*
    链接

    李永京 2009-05-29 16:51:00

    泛型没有Box和unBox,性能就是比ArrayList高

  2. 老赵
    admin
    链接

    老赵 2009-05-29 16:52:00

    --引用--------------------------------------------------
    李永京: 泛型没有Box和unBox,性能就是比ArrayList高
    --------------------------------------------------------
    这里比的是Object,但还是比ArrayList高。

  3. Grove.Chu
    *.*.*.*
    链接

    Grove.Chu 2009-05-29 16:58:00

    支持老赵

  4. 颜斌
    *.*.*.*
    链接

    颜斌 2009-05-29 16:58:00

    @李永京 Box和unBox是在值类型和对象之间的转换发生的吧,如果泛型的所包容的是一般的对象,那这个原因不能说明问题。 在遍历的时候 ArrayList性能过低 是不是由于 ArrayList对于Index没有索引的原因?就是说,他在获取第5个元素的时候,是从第一个元素开始计数,计算到第五个就返回这个元素。个人猜测

  5. 老赵
    admin
    链接

    老赵 2009-05-29 16:59:00

    --引用--------------------------------------------------
    Grove.Chu: 支持老赵
    --------------------------------------------------------
    不要支持我,支持事实。

  6. 老赵
    admin
    链接

    老赵 2009-05-29 16:59:00

    --引用--------------------------------------------------
    颜斌: @李永京
    Box和unBox是在值类型和对象之间的转换发生的吧,如果泛型的所包容的是一般的对象,那这个原因不能说明问题。
    在遍历的时候 ArrayList性能过低 是不是由于 ArrayList对于Index没有索引的原因?个人猜测
    --------------------------------------------------------
    这“遍历”的时候本就不需要索引……

  7. 颜斌
    *.*.*.*
    链接

    颜斌 2009-05-29 17:01:00

    --引用--------------------------------------------------
    Jeffrey Zhao: --引用--------------------------------------------------
    颜斌: @李永京
    Box和unBox是在值类型和对象之间的转换发生的吧,如果泛型的所包容的是一般的对象,那这个原因不能说明问题。
    在遍历的时候 ArrayList性能过低 是不是由于 ArrayList对于Index没有索引的原因?个人猜测
    --------------------------------------------------------
    这“遍历”的时候本就不需要索引……
    --------------------------------------------------------
    就当我瞎扯了...

  8. 颜斌
    *.*.*.*
    链接

    颜斌 2009-05-29 17:03:00

    yield造成的?我也不知道为什么,今天特别有心情回复

  9. Kevin Dai
    *.*.*.*
    链接

    Kevin Dai 2009-05-29 17:04:00

    呵呵,C#的泛型作的很好很强大啊,不然还不怎么好意思拿出来上台面呢。(相对于java中的泛型)

  10. 老赵
    admin
    链接

    老赵 2009-05-29 17:04:00

    --引用--------------------------------------------------
    颜斌: yield造成的?我也不知道为什么,今天特别有心情回复
    --------------------------------------------------------
    ArrayList出现的时候还没有yield。
    // 不要光猜,先调查验证再说吧。

  11. 幸存者[未注册用户]
    *.*.*.*
    链接

    幸存者[未注册用户] 2009-05-29 17:09:00

    因为ArrayList需要casting吗?呵呵,手头上没有Visual Studio没法验证了

  12. 温景良(Jason)
    *.*.*.*
    链接

    温景良(Jason) 2009-05-29 17:11:00

    今天又让我明白了,以前也认为arraylist性能比list<object>高.谢谢,老赵

  13. 老赵
    admin
    链接

    老赵 2009-05-29 17:14:00

    --引用--------------------------------------------------
    温景良(Jason): 今天又让我明白了,以前也认为arraylist性能比list<object>高.谢谢,老赵
    --------------------------------------------------------
    其实试验一下很快,我做这个实验用了15分钟不到,写这篇文章倒用了半个多小时……

  14. 李永京
    *.*.*.*
    链接

    李永京 2009-05-29 17:16:00

    --引用--------------------------------------------------
    Kevin Dai: 呵呵,C#的泛型作的很好很强大啊,不然还不怎么好意思拿出来上台面呢。(相对于java中的泛型)
    --------------------------------------------------------
    Java中的泛型是假的~~~Java在编译的时候用使用类型替代了,而.Net是使用一个标志。属于真正的泛型

  15. abatei
    *.*.*.*
    链接

    abatei 2009-05-29 17:25:00

    记得MSDN有这么段文字
    List的速度已经和数组相当
    强于ArrayList

  16. Galactica
    *.*.*.*
    链接

    Galactica 2009-05-29 17:28:00

    CodeTimer 很好玩嘛!

  17. 老赵
    admin
    链接

    老赵 2009-05-29 17:28:00

    @abatei
    其实ArrayList也是数组,不是吗?

  18. 幸存者[未注册用户]
    *.*.*.*
    链接

    幸存者[未注册用户] 2009-05-29 17:33:00

    --引用--------------------------------------------------
    李永京: --引用--------------------------------------------------
    Kevin Dai: 呵呵,C#的泛型作的很好很强大啊,不然还不怎么好意思拿出来上台面呢。(相对于java中的泛型)
    --------------------------------------------------------
    Java中的泛型是假的~~~Java在编译的时候用使用类型替代了,而.Net是使用一个标志。属于真正的泛型
    --------------------------------------------------------
    如果不考虑primitive type(C#看叫value type)的话,.NET的泛型和Java的泛型是一样的。

  19. 老赵
    admin
    链接

    老赵 2009-05-29 17:35:00

    @幸存者
    据我了解李永京说的是正确的,您看到的资料能给我们看一下吗?
    Java的primitive type和.net的value type也不一样。Java的int不是对象,.net的int是对象。

  20. JinweiLee
    *.*.*.*
    链接

    JinweiLee 2009-05-29 17:54:00

    老赵分析的有道理。。

  21. 幸存者[未注册用户]
    *.*.*.*
    链接

    幸存者[未注册用户] 2009-05-29 18:02:00

    @Jeffrey Zhao
    我说一样是指,当.NET中的List<T>的T为reference type时,内存中只有一份List<T>的代码,所有List<T>都共享同一份代码。
    区别当然也是有的,因为.NET中的List<T>是在JIT时实例化的,而Java是的编译时,因为JVM层面没有泛型的概念,因此,Java中泛型参数是不允许primitive type的。
    至于真假,我不认为不同的实现方式有真假之说。

  22. 老赵
    admin
    链接

    老赵 2009-05-29 18:04:00

    @幸存者
    嗯,其实他的意思是:一个是编译器的语法糖,一个是运行时的改进。
    所以.NET在性能上会有改进(就算引用类型也有改进,免除一次Cast了),Java只是语法上的简化。

  23. m j[未注册用户]
    *.*.*.*
    链接

    m j[未注册用户] 2009-05-29 19:16:00

    我也按文中的方法测试了一下,没深入具体实现,只从表面现象说说我的想法:

    foreach (object o in myArrayList.InnerContainer)
    {
    var o1 = o;
    }
    就等价于
    IEnumerator en = myArrayList.InnerContainer.GetEnumerator();
    object o;
    while (en.MoveNext())
    {
    o = en.Current;
    var o1 = o;
    }
    测试发现IEnumerator.Current和IEnumerator<Object>.Current有较大的性能差别,难道是多了一次类型转换?

    我在测试时还发现把IEnumerator en = myArrayList.InnerContainer.GetEnumerator();提到计时器外,则遍历几乎不需要时间,貌似有高质量的缓存

    期待老赵下文。。

  24. 老衲2009[未注册用户]
    *.*.*.*
    链接

    老衲2009[未注册用户] 2009-05-29 19:37:00

    其实Jeffrey Ritcher 也在 《CLR via C#》一书中提到,Microsoft推荐开发人员使用泛型集合替代旧的非泛型集合,理由是使用泛型集合能获得类型安全性,让程序更加清晰,并且能获得更好的性能。

  25. Ariex[未注册用户]
    *.*.*.*
    链接

    Ariex[未注册用户] 2009-05-29 19:40:00

    java泛型集合里面的remove和contains系列方法都是object参数,貌似只是省去了显示拆箱而已……

  26. 幸存者[未注册用户]
    *.*.*.*
    链接

    幸存者[未注册用户] 2009-05-29 20:08:00

    --引用--------------------------------------------------
    Ariex: java泛型集合里面的remove和contains系列方法都是object参数,貌似只是省去了显示拆箱而已……
    --------------------------------------------------------
    拆装箱和类型转换还是有区别的

  27. rosanshao
    *.*.*.*
    链接

    rosanshao 2009-05-29 20:35:00

    范型真的会降低性能吗?我觉得这个性能不是指访问集合的性能,而是整个应该程序的性能。 使用了范型,编译器会自己生成一些新类,如果过度使用范型会使整程序的大小增加很多。

  28. 老赵
    admin
    链接

    老赵 2009-05-29 20:58:00

    @rosanshao
    对于List<T>,如果T是引用类型,那么是共享一份代码的。如果是值类型,那每种类型一份代码。而且这些都是运行时的情况,编译器是不会生成新类的,这个必须搞清楚。
    进一步讲,你为什么所程序大小会增加“很多”呢?有什么评测依据吗?而且,程序大小增大了,整个应用程序的性能会不会降低?
    我可以负责任地说,有10个类型的应用程序,和1000个类型的应用程序,相同的代码运行效率是一样的,不信的话我们可以再试试看,呵呵。

  29. JimLiu
    *.*.*.*
    链接

    JimLiu 2009-05-29 21:09:00

    泛型万岁!
    IEnumerable<T> + Extension Methods + Linq = 舒坦!

  30. volnet(可以叫我大V)
    *.*.*.*
    链接

    volnet(可以叫我大V) 2009-05-29 21:35:00

    只有一个意见要提,那就是:
    老赵,你的代码要不要考虑改一下呢?
    我是指放在页面上,因为我用QQ邮箱阅读的时候,会变成整个代码会在一条线上……

    形容:
    public class MyArrayList{ public MyArrayList(int length) { this.m_items = new object[length]; } public object[] m_items; public object this[int index] { get { return this.m_items; } set { this.m_items = value; } } public IEnumerable InnerContainer { get { return this.m_items; } }}public class MyList<T>{ public MyList(int length) { this.m_items = new T[length]; } public T[] m_items; public T this[int index] { get { return this.m_items; } set { this.m_items = value; } } public IEnumerable<T> InnerContainer { get { return this.m_items; } }}

  31. onion
    *.*.*.*
    链接

    onion 2009-05-29 21:36:00

    泛型类似于C++中的模板,目的是告诉编译器具体类型,编译器自动会根据指定的模板进行编译。所以应该说List<string>和StringList的编译结果应该是一样的,性能当然不会有所减弱。

  32. 老赵
    admin
    链接

    老赵 2009-05-29 21:38:00

    --引用--------------------------------------------------
    onion: 泛型类似于C++中的模板,目的是告诉编译器具体类型,编译器自动会根据指定的模板进行编译。所以应该说List<string>和StringList的编译结果应该是一样的,性能当然不会有所减弱。
    --------------------------------------------------------
    .NET中的范型不是这样实现的。

  33. 老赵
    admin
    链接

    老赵 2009-05-29 21:38:00

    @volnet(可以叫我大V)
    好多地方处理HTML都好随意阿,会把连续空白字符或换行变成一个空格。
    但是要知道对于pre元素是不能随意去掉的。
    这也是为什么我以前写程序都会用<br/>,而不是用pre的原因,因为博客园的编辑器会自动去空白。
    现在我用copy from VS这个插件了,难道还要换回以前的……
    pre对于编辑真的很有效,因为换行都直接看得出来。

  34. volnet(可以叫我大V)
    *.*.*.*
    链接

    volnet(可以叫我大V) 2009-05-29 21:42:00

    @Jeffrey Zhao
    算了,你不要改了,哈哈,我自己的博客也是一条线~

  35. xuefly
    *.*.*.*
    链接

    xuefly 2009-05-29 21:56:00

    ArrayList和List<T>遍历的性能差异可能来自:

    一:引用类型和值类型的差异
    因为用Reflector发现它俩对IEnumerator接口的实现,一个是用的struct一个用的class。但经测试访问struct中的值类型(在栈中)和访问class中的值类型(在堆中)性能差异不明显。
    二:业务逻辑问题
    ArrayList的MoveNext()里面比List<T>多出来一个if(IsArrayList)判断。经测试if(IsArrayList)判断是导致ArrayList性能差的最主要因素!

  36. 老赵
    admin
    链接

    老赵 2009-05-29 22:08:00

    @xuefly
    奇怪,为什么判断IsArrayList反而导致性能差?

  37. Gray Zhang
    *.*.*.*
    链接

    Gray Zhang 2009-05-29 22:08:00

    @Jeffrey Zhao
    老赵,摘抄一段CLR via C#里对泛型的解释,我对这话的理解是泛型会生成新的类型,不知道你怎么看的:
    When you use a generic type and specify type arguments, you are defining a new type object in the CLR, and the new type object is derived from whatever type the generic type was derived from.

  38. 老赵
    admin
    链接

    老赵 2009-05-29 22:10:00

    @Gray Zhang
    真要说起来,这样说也没错,的确是一个新类型,例如以下都是不同类型:
    typeof(List<>)
    typeof(List<int>)
    typeof(List<string>)
    但是,对于引用类型来说,例如List<string>和List<Object>,他们在运行时是共享同一份可执行代码的,这点应该没有疑义吧?不知道是否还需要再验证一遍。
    方法表是否是同一个,这个我就不知道了,有机会也验证一下。

  39. Gray Zhang
    *.*.*.*
    链接

    Gray Zhang 2009-05-29 22:21:00

    @Jeffrey Zhao
    嗯,代码段应当是同一份,我再回去更深地研究下泛型~

  40. Anders06
    *.*.*.*
    链接

    Anders06 2009-05-29 22:22:00

    好奇中。。。
    我记得应该是略慢一点点的~~~

  41. 老赵
    admin
    链接

    老赵 2009-05-29 22:22:00

    --引用--------------------------------------------------
    Gray Zhang: @Jeffrey Zhao
    嗯,代码段应当是同一份,我再回去更深地研究下泛型~
    --------------------------------------------------------
    不过值类型就是不同的了,一个值类型一份可执行代码。

  42. Teddy's Knowledge Bas…
    *.*.*.*
    链接

    Teddy's Knowledge Base 2009-05-29 22:36:00

    老赵的测试极度不公平!!

    如果都用For而不是foreach,我想应该非泛型略快一点点.

    同一个数组泛型和非泛型时的Enumerator不同.
    System.Array+SZArrayEnumerator
    System.SZArrayHelper+SZGenericArrayEnumerator`1[System.Object]

    用Reflector看他们的实现,大多数代码一样,唯一的区别是,
    System.Array+SZArrayEnumerator.Current属性的实现:
    return this._array.GetValue(this._index);

    而System.SZArrayHelper+SZGenericArrayEnumerator`1[System.Object].Current属性的实现:
    return this._array[this._index];

    注意,前一个_array是Array类型的,后一个是object[]类型的.

    也就是说,非泛型时,getvalue需要执行Array.GetValue()也就是下面这段额外的代码:
    public unsafe object GetValue(int index)
    {
    if (this.Rank != 1)
    {
    throw new ArgumentException(Environment.GetResourceString("Arg_Need1DArray"));
    }
    TypedReference reference = new TypedReference();
    this.InternalGetReference((void*) &reference, 1, &index);
    return TypedReference.InternalToObject((void*) &reference);
    }

    而泛行直接一个索引就返回值了.你说多不公平啊~~

  43. 老赵
    admin
    链接

    老赵 2009-05-29 22:39:00

    @Teddy's Knowledge Base
    夸张了,有啥不公平的,还“极度”不公平。
    我测试的都是平时的常用操作,黑盒测试,又没有故意去走某个特别慢的路径。
    反而是你的方法“极度不公平”地想要给非范型平反呢,呵呵。
    看来内部实现的造成问题,可惜我们平时都是用这个做法,因为是框架自带的。

    为什么总是说范型性能会差一些?测试结果都摆在这里了。
    根据测试结果,就算是下标访问,范型也不差啊(这个结果怎么被你忽略了)。
    所以结论还是不变,能够用List<Object>就不要用ArrayList。

  44. xuefly
    *.*.*.*
    链接

    xuefly 2009-05-29 22:43:00

    --引用--------------------------------------------------
    Jeffrey Zhao: @xuefly
    奇怪,为什么判断IsArrayList反而导致性能差?
    --------------------------------------------------------
    我看代码,判断IsArrayList并没有减少任何代码的执行。从ArrayList对IsArrayList的判断看来:.NET Framework团队不反对我们继承ArrayList。
    从下面"ArrayListEnumeratorSimple: IEnumerator”的构造函数块中的第四行代码看的
    internal ArrayListEnumeratorSimple(ArrayList list)
    {
    this.list = list;
    this.index = -1;
    this.version = list._version;
    this.isArrayList = list.GetType() == typeof(ArrayList);
    this.currentElement = dummyObject;
    }
    看代码逻辑猜测:.NET Framework团队鼓励我们通过继承ArrayList来实现强类型的ArrayList集合。
    public bool MoveNext()
    {
    if (this.version != this.list._version)
    {
    throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumFailedVersion"));
    }
    if (this.isArrayList)
    {
    if (this.index < (this.list._size - 1))
    {
    this.currentElement = this.list._items[++this.index];
    return true;
    }
    this.currentElement = dummyObject;
    this.index = this.list._size;
    return false;
    }
    if (this.index < (this.list.Count - 1))
    {
    this.currentElement = this.list[++this.index];
    return true;
    }
    this.index = this.list.Count;
    this.currentElement = dummyObject;
    return false;
    }

  45. Teddy's Knowledge Bas…
    *.*.*.*
    链接

    Teddy's Knowledge Base 2009-05-29 22:50:00

    对于*通用*的编程习惯来说,你的结论可以接受.不过,通过从内部实现来说,使用下标方式应该基本是一样的,你说互有胜负就对了,理该如此.但是泛型在编译时会多出一些类的,也算是某一方面的额外开销.

    我的建议是,新手应该注意for和foreach的区别,虽然说,同样作为*通用*的编程习惯,很多人都建议使用foreach代替for,但是从你的测试可以看到,如果只是纯粹的遍历,for的性能还是比foreach更好的,还好了不少呢.即时是泛型情况下.

  46. 老赵
    admin
    链接

    老赵 2009-05-29 22:55:00

    @Teddy's Knowledge Base
    嗯嗯,从性能角度来说,下标访问的确会高。但是我还是推荐使用foreach。原因如下:
    1、性能相差实在太少,就一些方法调用而已。
    2、代码优雅,语义精美。
    3、IEnumerable<T>接口实在太美妙,内部实现完全隐藏,想延迟就延迟,想通用就通用,甚至可以写成支持并发的遍历。

    当然初学者,是一定要分清for和foreach的。

  47. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-05-29 23:01:00

    所谓泛型有性能损耗 是指泛型相对于直接类型的类比较
    List<String>比StringList慢一点

    经常有人这么提 是因为C++中就不存在这个性能差异 C#的这个微小变化就容易被忽略

    你如果拿List<String>跟ObjectList存储String比 自然其中有很大随机性了

  48. 老赵
    admin
    链接

    老赵 2009-05-29 23:05:00

    @winter-cn
    我这里是拿MyList<Object>和MyArrayList(就是ObjectList)相比,为什么没有看出这个性能差别?
    这个性能差别是由什么引起的?

  49. Teddy's Knowledge Bas…
    *.*.*.*
    链接

    Teddy's Knowledge Base 2009-05-29 23:10:00

    我的意见是这样的,泛型,尤其是这组泛型的集合类型,它们的最大好处是,当作为对外接口时,可以尽可能隐藏内部实现,而只暴露最必要的集合的逻辑语义,而遍历的性能,如你所说所测,也是可以做到和for差异很小的.所以,如果是组件或系统间对外暴露的方法参数或返回值类型,因尽可能使用泛型的集合接口.

    而作为一个组件的内部方法实现,特别是private的方法,如果能使用数组和下标,能for,建议直接使用数组作为参数和返回值和for来遍历.

    诚然,对大多数应用来说,这一点性能差异可能不算什么,不过,嘿,即使是对泛型下实现最优的遍历算法,根据你的测试,对数组的for也比对通用泛型集合的foreach性能好25%以上.对一个稍大的系统来说,累计的开销也是有一点点地.

  50. 老赵
    admin
    链接

    老赵 2009-05-29 23:17:00

    @Teddy's Knowledge Base
    也只有纯粹的大量运算中,会产生一点差距。
    对于普通的“稍大”的系统,比如随便说一个“企业应用”,说瓶颈在这点“运算”上,实在不靠谱。
    我随口说一个数字,99%的系统不会受这个影响,应该没有问题,嘿嘿。

  51. Jacky.Zhou
    *.*.*.*
    链接

    Jacky.Zhou 2009-05-29 23:58:00

    根据很多.NET文献,都不支持使用ArrayList,而支持使用泛型!

  52. clayman
    *.*.*.*
    链接

    clayman 2009-05-30 04:00:00

    差别的原因在于虚函数,ms在2.0的时候为泛型重写了collection,减少了虚函数,但为了不破坏已有代码,没有修改ArrayList。我在飞林沙blog的留言里挂了详细分析的链接,感兴趣可以去看看:)

  53. xuefly
    *.*.*.*
    链接

    xuefly 2009-05-30 07:59:00

    --引用--------------------------------------------------
    clayman: 差别的原因在于虚函数,ms在2.0的时候为泛型重写了collection,减少了虚函数,但为了不破坏已有代码,没有修改ArrayList。我在飞林沙blog的留言里挂了详细分析的链接,感兴趣可以去看看:)
    --------------------------------------------------------
    在一个老外的回复中说中要害了:
    If you were to dig deeper in this you'd find that the reasons it being slower break down into two categories:

    1) It has safety checks to help you if the collection is modified out from under you(generally a good tradeoff)

    2) It doesn't get fully inlined (partly due to the above)

    With ArrayList the problem, as I've mentioned, is that the implementation choice was not especially good.

    In neither case is there any find of "foreach" issue -- it's question of how the enumerator is implemented and what choices were made there. You could even write your own arraylist wrapper value type (no storage overhead) that makes a different choice and get different behavior. You could have the for behavior if you want it.
    #44楼
    我看代码,判断IsArrayList并没有减少任何代码的执行。从ArrayList对IsArrayList的判断看来:.NET Framework团队不反对我们继承ArrayList。

    看代码逻辑猜测:.NET Framework团队鼓励我们通过继承ArrayList来实现强类型的ArrayList集合。

  54. 深山老林
    *.*.*.*
    链接

    深山老林 2009-05-30 12:05:00

    泛型的推出就明确表示性能上做了重大改进,没有装箱与拆箱造成的性能损耗,而飞林沙同学提到,使用泛型会略微降低程序性能,因此在程序中使用List<Object>是不合理的行为,应该使用ArrayList。的确这个观点曲解了现实,对读者来说有强烈的误导作用。老赵指出他的错误来,应该能够挽救一大批被错误观念引导的程序员吧,在以前老赵就指出过我观念上的错误,至今想起来都感激万分,在此再次向老赵表示感谢。

  55. 罗立树
    *.*.*.*
    链接

    罗立树 2009-05-30 15:39:00

    泛型一般都是在编译期监测的,也就是在编译的时候已经确定了,这样不会对性能有影响,有时候通过泛型可以指明数据的类型,从而避免了在运行时的强制转换,这样反而提升了性能

  56. 老赵
    admin
    链接

    老赵 2009-05-30 15:47:00

    --引用--------------------------------------------------
    罗立树: 泛型一般都是在编译期监测的,也就是在编译的时候已经确定了,这样不会对性能有影响,有时候通过泛型可以指明数据的类型,从而避免了在运行时的强制转换,这样反而提升了性能
    --------------------------------------------------------
    .net中的泛型是运行时的。

  57. 装配脑袋
    *.*.*.*
    链接

    装配脑袋 2009-05-30 19:21:00

    Java泛型无论是不是基本类型或值类型。和.NET泛型都是完全不同的。有太多Java泛型不能做到的东西。不想赘述了。

  58. 无常
    *.*.*.*
    链接

    无常 2009-05-30 20:22:00

    当在多个foreach迭代时,List<T>明显快于ArrayList ,
    并不是因为明显的装箱/拆箱导致的,而是因为ArrayList无故使用了大量的虚方法,尤其是在枚举时更为严重。ArrayList.GetEnmerator()是虚方法,并且嵌套的枚举容易ArrayListEnumeratorSimple还虚拟地实现了MoveNext()方法和Current属性。在枚举时这些为函数的调用增添了许多开销高昂的虚方法。

    ------《C#捷径教程》Trey Nash著,刘新军译.人民邮电出版社,09年2月第一版 P207
    Rico Mariani博客中有说明
    http://blogs.msdn.com/ricom/archive/2005/08/25/performance-quiz-7-generics-improvements-and-costs.aspx

    http://blogs.msdn.com/ricom/archive/2005/08/26/performance-quiz-7-generics-improvements-and-costs-solution.aspx


    --------------
    刚才在选教材,翻看到容器和迭代器这一章,想到昨晚看到的这个问题,
    特上来补充一下

  59. 老赵
    admin
    链接

    老赵 2009-05-30 20:28:00

    @无常
    没错,这两篇文章里说的很清楚了,ArrayList和List<T>的foreach性能差距是因为这个原因。
    可笑的是,在我下一篇文章,想说明“ArrayList和List<T>的下标访问”是不是在性能上有差距,却有人拿这两篇文章来反驳,说可以不用汇编就能点名问题关键。
    之前都提醒他要看清文章中心,他却还一再坚持,还认为别人没有看他给的文章,实在让我非常反感。

  60. 别爱上哥,哥只是个传说!
    *.*.*.*
    链接

    别爱上哥,哥只是个传说! 2009-05-31 09:51:00

    鸡蛋原理.没有必要,以现在计算机硬件的配置来说,数据量不是巨大的话,根本这点时间可以忽略不计哇..
    如果要研究性能,建议去用汇编直接写程序.那样性能极高,真滴.

  61. 不死鸟之魂
    *.*.*.*
    链接

    不死鸟之魂 2009-05-31 09:52:00

    如果泛型是在编译阶段就已经确定了类型的话,性能当然会比ArrayList这种非泛型的要高。

  62. 老赵
    admin
    链接

    老赵 2009-05-31 09:53:00

    @不死鸟之魂
    .NET中泛型是运行时的功能。

  63. 老赵
    admin
    链接

    老赵 2009-05-31 09:54:00

    --引用--------------------------------------------------
    别爱上哥,哥只是个传说!: 鸡蛋原理.没有必要,以现在计算机硬件的配置来说,数据量不是巨大的话,根本这点时间可以忽略不计哇..
    如果要研究性能,建议去用汇编直接写程序.那样性能极高,真滴.
    --------------------------------------------------------
    一般人写出的C程序,经编译器优化的结果比自己写汇编性能要高。

  64. 不死鸟之魂
    *.*.*.*
    链接

    不死鸟之魂 2009-05-31 09:55:00

    --引用--------------------------------------------------
    颜斌: @李永京
    Box和unBox是在值类型和对象之间的转换发生的吧,如果泛型的所包容的是一般的对象,那这个原因不能说明问题。
    在遍历的时候 ArrayList性能过低 是不是由于 ArrayList对于Index没有索引的原因?就是说,他在获取第5个元素的时候,是从第一个元素开始计数,计算到第五个就返回这个元素。个人猜测
    --------------------------------------------------------
    这个可能性不大。就算MS为了对抗Java而冲忙推出1.1存在这个Bug,但是到了2.0就应该会修正。
    在相似的操作中使用两种不同的方法的可能性不大。

  65. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-05-31 11:04:00

    --引用--------------------------------------------------
    颜斌: @李永京
    Box和unBox是在值类型和对象之间的转换发生的吧,如果泛型的所包容的是一般的对象,那这个原因不能说明问题。
    在遍历的时候 ArrayList性能过低 是不是由于 ArrayList对于Index没有索引的原因?就是说,他在获取第5个元素的时候,是从第一个元素开始计数,计算到第五个就返回这个元素。个人猜测
    --------------------------------------------------------

    Jeffery Richard上次.net技术大会 说过
    c#对于 对于 list 的foreach编译 其实调用了
    for(i++) 来提速

    貌似 arraylist 没有做优化哦

  66. 老赵
    admin
    链接

    老赵 2009-05-31 11:07:00

    @韦恩卑鄙
    啊,是吗?我只记得对数组的foreach有优化,一会儿观察一下去。
    印象中在Refactor中看到过很多对List<T>的foreach代码,所以现在还不太相信这个说法……

  67. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-05-31 11:51:00

    JR说一定是在release时候才作 咱们平时Refactor 往往是debug所以容易忽略

  68. 老赵
    admin
    链接

    老赵 2009-05-31 11:53:00

    @韦恩卑鄙
    但是我平时Refactor的对象是.NET Framework……

  69. wdong525
    *.*.*.*
    链接

    wdong525 2009-05-31 12:13:00

    我是.net的初学者,
    想知道在.net方面有什么好一点的书可以看啊

  70. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-05-31 12:16:00

    恩 所以说写.NET Framework的时候 这个编译器优化还没出来?

    这个可真有趣了 :D

  71. wdong525
    *.*.*.*
    链接

    wdong525 2009-05-31 12:18:00

    不知道为什么
    我还是感觉泛型用起来比集合好一些最起码不要类型转换。
    但是它只可以装特定的数据类型
    这点我不是很喜欢
    如果它也可以装Object的对象就更好了

  72. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-05-31 12:20:00

    <object>@wdong525

  73. 老赵
    admin
    链接

    老赵 2009-05-31 13:00:00

    @韦恩卑鄙
    我看的是ASP.NET MVC,ASP.NET AJAX的代码。

  74. 老赵
    admin
    链接

    老赵 2009-05-31 13:01:00

    @韦恩卑鄙
    试了一下,发现很有意思,因为无论是List还是[]都没有优化。

    List<int> list = Enumerable.Repeat(0, 10).ToList();
    foreach (var i in list) Console.WriteLine(i);

    int[] array = Enumerable.Repeat(0, 10).ToArray();
    foreach (var i in array) Console.WriteLine(i);

    难道是JIT的优化,那又要动用汇编才看得出来了。

  75. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-05-31 14:20:00

    @Jeffrey Zhao
    我还真的没反编看 JR说得也很含混 只说了优化

    就这个事实他在第一讲还说了将近5分钟 似乎是他也没发现哪里做优化 直接问的c#组长才得到答案

  76. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-05-31 14:22:00

    对了 Anders.Cui 为了给同事作培训 在公司做实验
    发现 foreach(var x in List<T>) 是有优化的
    list<T>.foreach 没有优化。
    参考下赫赫

  77. 老赵
    admin
    链接

    老赵 2009-05-31 14:28:00

    @韦恩卑鄙
    我的代码不就是foreach (var x in List<T>)吗?

  78. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-05-31 16:02:00

    是啊 没错 所以性能才体现了差距

    我就是顺带一提这个事情
    bs 没有优化彻底的c#项目组

  79. 老赵
    admin
    链接

    老赵 2009-05-31 16:04:00

    @韦恩卑鄙
    我是说我在74页的例子为什么没有得到优化,而Anders Cui的例子就优化了。

  80. 别爱上哥,哥只是个传说!
    *.*.*.*
    链接

    别爱上哥,哥只是个传说! 2009-05-31 16:35:00

    --引用--------------------------------------------------
    Jeffrey Zhao: --引用--------------------------------------------------

    别爱上哥,哥只是个传说!: 鸡蛋原理.没有必要,以现在计算机硬件的配置来说,数据量不是巨大的话,根本这点时间可以忽略不计哇..

    如果要研究性能,建议去用汇编直接写程序.那样性能极高,真滴.

    --------------------------------------------------------

    一般人写出的C程序,经编译器优化的结果比自己写汇编性能要高。
    --------------------------------------------------------

    那就用C写..

  81. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-05-31 17:28:00

    --引用--------------------------------------------------
    Jeffrey Zhao: @韦恩卑鄙
    我是说我在74页的例子为什么没有得到优化,而Anders Cui的例子就优化了。
    --------------------------------------------------------
    是我没说明白

    他测试得到优化也没有在IL上得到验证 是结果上的速度差出现了:D

  82. 老赵
    admin
    链接

    老赵 2009-05-31 17:31:00

    @韦恩卑鄙
    难道真的是JIT的优化,难以置信阿,要优化也应该是C#编译器优化才对,大家各做各的事情啊。
    他的代码哪里可以看到吗?召唤Anders Cui……

  83. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-05-31 18:15:00

    老赵的文章实在是太较真了,估计要惹很多同学不高兴了。
    精神值得学习啊,像你这样的人,在团队里估计很不受欢迎。
    实话实说,别生气

  84. 老赵
    admin
    链接

    老赵 2009-05-31 18:21:00

    @DiggingDeeply
    这篇文章有啥理由惹人不高兴?没有任何不当的措辞吧。

  85. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-05-31 18:39:00

    那些靠“性能”“框架”为生的同学估计难混了,不能到砂锅问到底啊。
    我觉得.Net里性能这东西,就看JIT怎么转换了,直接决定了性能。

  86. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-05-31 18:41:00

    忘了说了一点,我认为CSC只是将C#翻译为IL,可能只会存在一定程度的优化;至于JIT,则是真正干活的人,当然它的方式和方法决定了效率。

  87. 老赵
    admin
    链接

    老赵 2009-05-31 18:42:00

    @DiggingDeeply
    没听懂……
    其实性能这东西最容易比较了吧,直接试验一下就可以了,如果有异议,那么可以提出另外一种试验方式,最直接。
    难混的应该还是那些光耍嘴皮子的人,但是如果他们好混了,那么这世道不就变了么。技术终究是技术。
    .net性能其实不错的,jit也不错的,程序性能终究还是看怎么写。
    比如ArrayList的性能差,也是因为写得关系,不是.net代码或jit的关系。

  88. 老赵
    admin
    链接

    老赵 2009-05-31 18:43:00

    --引用--------------------------------------------------
    DiggingDeeply: 忘了说了一点,我认为CSC只是将C#翻译为IL,可能只会存在一定程度的优化;至于JIT,则是真正干活的人,当然它的方式和方法决定了效率。
    --------------------------------------------------------
    只能说“影响”了效率,比C#编译器影响得更大一些,不过说“决定”了效率有点过了。
    还是要看程序怎么写,例如你写程序的时间复杂度如何,并行性如何。
    所以编译器对性能的影响也不好说,因为编译器了解的东西多(例如可以覆盖到整个程序集甚至更高),可以改变程序做法,因此对性能影响也可以很高。

    不过我觉得我们的意思应该差不多,只是用词上不太一样。

  89. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-05-31 18:57:00

    --引用--------------------------------------------------
    Jeffrey Zhao: --引用--------------------------------------------------

    DiggingDeeply: 忘了说了一点,我认为CSC只是将C#翻译为IL,可能只会存在一定程度的优化;至于JIT,则是真正干活的人,当然它的方式和方法决定了效率。

    --------------------------------------------------------

    只能说“影响”了效率,比C#编译器影响得更大一些,不过说“决定”了效率有点过了。

    还是要看程序怎么写,例如你写程序的时间复杂度如何,并行性如何。

    所以编译器对性能的影响也不好说,因为编译器了解的东西多(例如可以覆盖到整个程序集甚至更高),可以改变程序做法,因此对性能影响也可以很高。



    不过我觉得我们的意思应该差不多,只是用词上不太一样。
    --------------------------------------------------------
    总结一句:编程就好比开赛车,不仅要车子性能好,还要看司机的驾驶技术。

  90. 老赵
    admin
    链接

    老赵 2009-05-31 19:04:00

    --引用--------------------------------------------------
    DiggingDeeply: 总结一句:编程就好比开赛车,不仅要车子性能好,还要看司机的驾驶技术。
    --------------------------------------------------------
    没错,说得好。

  91. GWPBrian
    *.*.*.*
    链接

    GWPBrian 2009-06-09 10:08:00

    Very Good!

  92. asheng
    *.*.*.*
    链接

    asheng 2009-06-11 13:57:00

    不好 不好
    老赵 这个博皮 看文章一点也不舒服 页面拉得太长 右边栏像广告一样的烦人 占太大地方了

  93. 菜鸟毛
    180.127.9.*
    链接

    菜鸟毛 2010-08-27 21:21:38

    老赵,从图片上看,真的老了不少!难道因为熬夜!?

  94. 凡非
    203.122.244.*
    链接

    凡非 2011-09-16 16:25:36

    这篇文章只能说List对比ArrayList性能不差。但不能说泛型性能不差。因为你没有看code来判断到底泛型有没有影响性能。但我并不是想说泛型性能就是差,这问题我也想求解。

  95. 凡非
    203.122.244.*
    链接

    凡非 2011-09-16 16:26:24

    List < Object > ,为什么《Object》没发上去

  96. shell
    114.83.63.*
    链接

    shell 2011-12-04 15:48:51

    结果让人惊讶。看来.NET为范型做了很多工作。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我