[.net泛型学习笔记之三]再测泛型的性能
脚本的问题
前一篇文章的测试脚本中,发现一个问题,请见下面的粗体部分。
public static double Test1()
{ GC.Collect();
ArrayList list1 = new ArrayList();
sw.Reset(); //sw是一个System.Diagnostics.Stopwatch对象
sw.Start();
for (int i = 0; i < Program.MaxCount; i++)
{ LargeDataStruct data1 = new LargeDataStruct();
list1.Add(data1); //测试装箱
LargeDataStruct data2 = (LargeDataStruct)list1[0]; //测试拆箱
}
sw.Stop();
return sw.Elapsed.TotalSeconds;
}
由于在集合的Add方法里,要判断当前集合的数量是否达到了集合的最大容量(见下面的代码),如果达到了,需要做相关的操作,而这个操作可能花费较多的时间。
//这是ArrayList的部分源代码
public virtual int Add(object value)
{
if (this._size == this._items.Length)
{
this.EnsureCapacity(this._size + 1); //花费很多时间
}
this._items[this._size] = value;
this._version++;
return this._size++;
}
private void EnsureCapacity(int min)
{
if (this._items.Length < min)
{
int num1 = (this._items.Length == 0) ? 4 : (this._items.Length * 2);
if (num1 < min)
{
num1 = min;
}
this.Capacity = num1;
}
}
新的测试脚本 下载
为了保证测试的独立性,把测试代码改成(下面的粗体部分,保证了集合的容量始终够用):
public static double Test1()
{ GC.Collect();
ArrayList list1 = new ArrayList(Program.MaxCount);
sw.Reset(); //sw是一个System.Diagnostics.Stopwatch对象
sw.Start();
for (int i = 0; i < Program.MaxCount; i++)
{ LargeDataStruct data1 = new LargeDataStruct();
list1.Add(data1); //测试装箱
LargeDataStruct data2 = (LargeDataStruct)list1[0]; //测试拆箱
}
sw.Stop();
return sw.Elapsed.TotalSeconds;
}
测试结果 下载
在P4 2.4GHz, 1G内存硬件,测试结果如下:
|
int in Arraylist |
int in List<T> |
large struct in ArrayList |
large class in ArrayList |
large struct in List<T> |
large class in List<T> |
string in ArrayList |
string in List<T> |
1000 |
0.0000949 |
0.0000387 |
0.001201 |
0.000836 |
0.000914 |
0.0009 |
0.00046 |
0.00042 |
10000 |
0.0006762 |
0.0002055 |
0.013376 |
0.008187 |
0.010115 |
0.0092 |
0.00427 |
0.00422 |
100000 |
0.007687 |
0.0017933 |
0.341669 |
0.30313 |
0.102465 |
0.3019 |
0.04894 |
0.04877 |
1000000 |
0.1269231 |
0.0186578 |
4.651395 |
4.524638 |
1.297351 |
4.5808 |
0.83491 |
0.83317 |
结果分析
l 避免装箱和拆箱测试
1. int in Arraylist vs. int in List<T>
List<T>比ArrayList快2-5倍。而且数据量越大,泛型的优势越大。
2. Large struct in ArrayList vs. Large class in ArrayList
后者比前者快一些,提高幅度在10%-50%之间。但数据量越大,差距越小。
3. Large struct in ArrayList vs. large struct in List<T>
List<T>比ArrayList快30%-300%,数据量越大,泛型的优势越大。
l 类型检查是在编译时间进行,而不是在运行时间进行的
1. custom class in ArrayList vs. custom class in List<T> (custom class 即large class )
ArrayList比List<T>速度稍快,在10%范围以内,故总体相当。
2. string in Arraylist vs. string in List<T>
List<T>比Arraylist速度稍快,在10%范围以内,故总体相当。
总结
对于泛型在的性能情况,可以得出以下结论:
1) 对于int这样的简单值类型,泛型能够提高2-5倍的速度。数据量越大,越明显。
2) 对于复杂的值类型,泛型能够提高30%-300%。数据量越大,越明显。
3) 对于引用(Reference)类型,泛型和传统的方式速度相当。
由上面的结论,可以看出,由于减少了装箱和拆箱,泛型对于值类型的对象性能提升明显。而在在编译时间进行类型检查的方式并没有带来性能的提升。另外由于值类型分配在堆栈之上,所以我们也能看出值类型的分配与创建过程比引用类型(分配在堆上)快得多。
今天的结论和前一篇文章的最大的不同在于,复杂值类型的测试结果迥异,但究竟原因是什么呢?一开始就初始化集合的容量真的对性能影响有这么大吗?泛型中的EnsureCapacity(int)方法为何比非泛型的EnsureCapacity(int)慢那么多,原因何在?