C# 列表<;T>;。AddRange实现次优
分析我的C#应用程序表明在C# 列表<;T>;。AddRange实现次优,c#,.net,performance,list,addrange,C#,.net,Performance,List,Addrange,分析我的C#应用程序表明在List.AddRange中花费了大量时间。使用Reflector查看此方法中的代码表明它调用了List.InsertRange,其实现如下: public void InsertRange(int index, IEnumerable<T> collection) { if (collection == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgum
List.AddRange
中花费了大量时间。使用Reflector查看此方法中的代码表明它调用了List.InsertRange
,其实现如下:
public void InsertRange(int index, IEnumerable<T> collection)
{
if (collection == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
}
if (index > this._size)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_Index);
}
ICollection<T> is2 = collection as ICollection<T>;
if (is2 != null)
{
int count = is2.Count;
if (count > 0)
{
this.EnsureCapacity(this._size + count);
if (index < this._size)
{
Array.Copy(this._items, index, this._items, index + count, this._size - index);
}
if (this == is2)
{
Array.Copy(this._items, 0, this._items, index, index);
Array.Copy(this._items, (int) (index + count), this._items, (int) (index * 2), (int) (this._size - index));
}
else
{
T[] array = new T[count]; // (*)
is2.CopyTo(array, 0); // (*)
array.CopyTo(this._items, index); // (*)
}
this._size += count;
}
}
else
{
using (IEnumerator<T> enumerator = collection.GetEnumerator())
{
while (enumerator.MoveNext())
{
this.Insert(index++, enumerator.Current);
}
}
}
this._version++;
}
private T[] _items;
你认为有什么理由不使用这个更简单、显然更快的替代方案吗
编辑:
谢谢你的回答。因此,大家一致认为,这是一种保护措施,可以防止输入集合以有缺陷/恶意的方式实现CopyTo。在我看来,不断地付出1)运行时类型检查2)临时数组的动态分配3)复制操作的两倍的代价似乎是一种过火的行为,当所有这些都可以通过定义2个或更多的InsertRange重载来保存时,一个得到IEnumerable
,另一个得到列表
,第三次获得T[]
。后两个系统的运行速度可能是当前情况的两倍
编辑2:
我确实实现了一个类FastList,与List相同,只是它还提供了一个AddRange重载,该重载采用了一个T[]参数。此重载不需要动态类型验证和元素的双重复制。我通过向一个最初是emtpy的列表中添加1000次4字节数组,对这个FastList.AddRange进行了分析。我的实现比标准List.AddRange的速度快9倍(9!)。在我们应用程序的一个重要使用场景中,List.AddRange占用了大约5%的运行时间,用提供更快AddRange的类替换List可以将应用程序运行时间提高4%。这是一个好问题,我很难找到原因。参考源中没有任何提示。一种可能性是,当实现ICollection.CopyTo()方法的类反对复制到非0的开始索引时,它们试图避免出现问题。或者作为一种安全措施,防止集合与它不应该访问的数组元素发生冲突 另一个原因是,当集合以线程不安全的方式使用时,这是一种应对措施。如果一个项目被另一个线程添加到集合中,则失败的将是集合类的CopyTo()方法,而不是Microsoft代码。合适的人会接到服务电话
这些都不是很好的解释。如果您仔细考虑一下,您的解决方案就会出现问题,如果您以这种方式更改代码,那么实际上就是在给应该添加的集合提供对内部数据结构的访问权 这不是一个好主意,例如,如果列表数据结构的作者找到了一个比数组更好的底层结构来存储数据,那么就没有办法更改列表的实现,因为所有集合都希望CopyTo函数中包含一个数组
本质上,您将巩固List类的实现,尽管面向对象编程告诉我们,数据结构的内部实现应该是可以在不破坏其他代码的情况下进行更改的。它们阻止了
ICollection
的实现访问插入范围之外的目标列表的索引。如果调用了CopyTo
的错误(或“操纵性”)实现,则上述实现会导致IndexOutOfBoundsException
请记住,T[].CopyTo
实际上是作为memcpy
在内部实现的,因此添加该行的性能开销非常小。当您为大量呼叫增加安全性的成本如此之低时,您不妨这样做
编辑:我觉得奇怪的是,调用ICollection.CopyTo
(复制到临时数组)不会在调用EnsureCapacity
后立即发生。如果将其移动到该位置,则该列表将保持不变。按原样,只有当插入发生在列表末尾时,该条件才成立。这里的理由是:
- 所有必要的分配都发生在更改列表元素之前
- 对
的调用不能失败,因为Array.Copy
- 内存已分配
- 边界已经检查过了
- 源数组和目标数组的元素类型匹配
没有C++中使用的“复制构造函数”,它只是一个MimCPy < /LI> - 唯一可以引发异常的项是对
ICollection.CopyTo的外部调用以及调整列表大小和分配临时数组所需的分配。如果这三种情况都发生在移动插入元素之前,则更改列表的事务不能引发同步异常
- 最后一点注意:这解决了严格的异常行为——上述原理并没有增加线程安全性
InsertRange
性能的问题,我非常确信,与重新实现动态列表相比,通过重新设计算法可以更好地解决人们确实面临的任何性能问题。为了不让你认为我的严厉是消极的,请记住以下几点:
- 我不想受不了我的开发团队中喜欢这样的人
- 我当然希望我的团队中有人关心潜在的性能问题,并询问他们的代码可能产生的副作用。当这一点出现时,它就赢了——但只要人们提出问题,我就会驱使他们改变想法
is2.CopyTo(this._items, index);