脚本的问题
前一篇文章的测试脚本中,发现一个问题,请见下面的粗体部分。
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)慢那么多,原因何在?