据说ArrayList真的比List<T>快?测试一下下。
2009-06-14 14:05 JimLiu 阅读(4488) 评论(28) 编辑 收藏 举报时间:就刚才。
地点:我寝室。
人物:我。
起因
最近我好像跟“性能”、“效率”这些敏感词汇较上劲了,先汗一个。
因为某些奇异的原因,我又做了如下极端无聊的事情,那就是对List<T>与ArrayList的“性能”的一个小测试。
在这个测试中,重点在于对List<T>与ArrayList的[Add,遍历]性能作讨论,坚决不将话题牵扯到普通容器和泛型容器。
测试中用到了老赵的一个简单的性能计数器——CodeTimer。
由于值类型存在boxing/unboxing,泛型容器有得天独厚的优势,二者几乎不存在可比性,所以我没做值类型的测试。
最终测试结果都包含Debug/Release两个数值。
经过
这里我用的元素类型是我自己定义的一个类,其定义如下
class Class {
int a, b, c, d, e, f, g;
}
用于测试的容器有两个:System.Collections.ArrayList,以及System.Collections.Generic.List<Class>
容器定义为
ArrayList arrayList = new ArrayList();
List<Class> list = new List<Class>();
首先是测试Add的性能
测试代码是:
Class obj = new Class(); // 用这个来插入
int n1 = 60 * 10000; // 插入次数
CodeTimer.Time("ArrayList.Add", n1, () => {
arrayList.Add(obj);
});
CodeTimer.Time("List<Class>.Add", n1, () => {
list.Add(obj);
});
插入测试结果:
ArrayList.Add
Time Elapsed: 36ms
CPU Cycles: 59,072,030
Gen 0: 1
Gen 1: 1
Gen 2: 1
List<Class>.Add
Time Elapsed: 26ms
CPU Cycles: 41,694,040
Gen 0: 0
Gen 1: 0
Gen 2: 0
ArrayList.Add
Time Elapsed: 35ms
CPU Cycles: 54,320,590
Gen 0: 1
Gen 1: 1
Gen 2: 1
List<Class>.Add
Time Elapsed: 22ms
CPU Cycles: 36,750,110
Gen 0: 0
Gen 1: 0
Gen 2: 0
可以看出List<Class>比ArrayList插入快一些。
下面就是大家比较关注的遍历性能测试了。
这里有两种遍历,一种是for,一种是foreach。容器内的东西就是刚才插入的那一堆。
下面是测试代码:
int n2 = 60 * 5; // 遍历次数
CodeTimer.Time("ArrayList for(i)", n2, () => {
for (int i = 0; i < arrayList.Count; ++i) {
var tempObj = arrayList[i];
}
});
CodeTimer.Time("List<Class> for(i)", n2, () => {
for (int i = 0; i < list.Count; ++i) {
var tempObj = list[i];
}
});
CodeTimer.Time("ArrayList foreach", n2, () => {
foreach (Class it in arrayList) {
var tempObj = it;
}
});
CodeTimer.Time("List<Class> foreach", n2, () => {
foreach (Class it in list) {
var tempObj = it;
}
});
呃……然后是遍历的结果
ArrayList for(i)
Time Elapsed: 3,194ms
CPU Cycles: 5,250,408,900
Gen 0: 0
Gen 1: 0
Gen 2: 0
List<Class> for(i)
Time Elapsed: 3,124ms
CPU Cycles: 5,156,644,380
Gen 0: 0
Gen 1: 0
Gen 2: 0
ArrayList foreach
Time Elapsed: 5,911ms
CPU Cycles: 9,800,432,750
Gen 0: 0
Gen 1: 0
Gen 2: 0
List<Class> foreach
Time Elapsed: 2,971ms
CPU Cycles: 4,867,982,990
Gen 0: 0
Gen 1: 0
Gen 2: 0
ArrayList for(i)
Time Elapsed: 1,647ms
CPU Cycles: 2,697,106,390
Gen 0: 0
Gen 1: 0
Gen 2: 0
List<Class> for(i)
Time Elapsed: 652ms
CPU Cycles: 1,084,824,510
Gen 0: 0
Gen 1: 0
Gen 2: 0
ArrayList foreach
Time Elapsed: 5,377ms
CPU Cycles: 8,884,062,410
Gen 0: 0
Gen 1: 0
Gen 2: 0
List<Class> foreach
Time Elapsed: 1,163ms
CPU Cycles: 1,925,787,250
Gen 0: 0
Gen 1: 0
Gen 2: 0
这个结果有点出乎我意料,在Debug模式下,ArrayList和List<Class>几乎平手,差别在2%左右,foreach上ArrayList则完全败北。在Release模式下却发生了很大的变化:for方面List<Class>拥有非常惊人的优化幅度,ArrayList的for虽然也快了很多但是依然不及List<Class>;foreach方面ArrayList停滞不前,而List<Class>却进一步拉大差距。
考虑到有时候我们会这么做
int arrayListLen = arrayList.Count;
CodeTimer.Time("ArrayList for(i)", n3, () => {
for (int i = 0; i < arrayListLen; ++i) {
var tempObj = arrayList[i];
}
});
int listLen = list.Count;
CodeTimer.Time("List<Class> for(i)", n3, () => {
for (int i = 0; i < listLen; ++i) {
var tempObj = list[i];
}
});
这是我们的老师常常教我们的一种方法。结果如下:
ArrayList for(i)
Time Elapsed: 2,438ms
CPU Cycles: 3,975,979,180
Gen 0: 0
Gen 1: 0
Gen 2: 0
List<Class> for(i)
Time Elapsed: 2,470ms
CPU Cycles: 4,056,331,770
Gen 0: 0
Gen 1: 0
Gen 2: 0
ArrayList for(i)
Time Elapsed: 1,182ms
CPU Cycles: 1,962,797,610
Gen 0: 0
Gen 1: 0
Gen 2: 0
List<Class> for(i)
Time Elapsed: 552ms
CPU Cycles: 917,996,780
Gen 0: 0
Gen 1: 0
Gen 2: 0
在Debug模式下,ArrayList终于“雪耻”,以大约1.3%的微弱优势超过了List<Class>,但很可惜,到了Release下List<Class>没有给ArrayList留下任何的机会。
结果
在这个测试所能涵盖的范围内,我们找不到任何理由为ArrayList树立功德碑,List<Class>以压倒性的优势告诉我们选择它是正确的。
后话
对于值类型,泛型容器的效率是有目共睹的,我不想做那种比这个无聊的测试还无聊的无聊测试了。
对于这个测试中的for测试,对ArrayList是不公平的,因为那一句var tempObj = arrayList[i];编译器显然会把var编译成object,而我们期待的是Class,实际应用中这里势必要发生type casting,所以ArrayList的for在实际中效率负担还会多上那么一丁丁点。
对于这个测试中的foreach测试,对ArrayList还是不公平的,因为我们foreach的是(Class it),对于ArrayList内部的IEnumerable实现来说,我们从这个代码中很难说ArrayList做了怎样的type casting,而对于List<Class>来说这个type casting我们是知道,它显然是不用做的。
但是话说回来,ArrayList的type casting不就是我们讨厌的地方吗?那么还有什么理由不选择List<T>?