分析我的 C# 应用程序表明大量时间花费在List<T>.AddRange
. 使用 Reflector 查看此方法中的代码表明它调用了它,List<T>.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;
有人可能会争辩说,接口的简单性(只有一个 InsertRange 重载)证明了运行时类型检查和强制转换的性能开销是合理的。但是我指出的 3 行背后的原因可能是什么(*)
?我认为它可以重写为更快的替代方案:
is2.CopyTo(this._items, index);
您是否有理由不使用这种更简单且明显更快的替代方案?
编辑:
感谢您的回答。因此,共识意见是,这是针对以有缺陷/恶意方式实施 CopyTo 的输入集合的保护措施。对我来说,不断付出代价似乎有点过头了 1) 运行时类型检查 2) 临时数组的动态分配 3) 复制操作加倍,而所有这些都可以通过定义 2 个或更多的 InsertRange 重载来保存,一个IEnumerable
像现在一样,第二个得到一个List<T>
,第三个得到T[]
。后两者的运行速度可能是当前情况的两倍。
编辑2:
我确实实现了一个类 FastList,与 List 相同,只是它还提供了一个 AddRange 的重载,它接受一个 T[] 参数。这种重载不需要动态类型验证和元素的双重复制。我确实通过将 4 字节数组添加到最初为 emtpy 的列表中 1000 次来针对 List.AddRange 分析此 FastList.AddRange。我的实现比标准 List.AddRange 的速度快了 9 倍(九倍!)。在我们应用程序的一个重要使用场景中,List.AddRange 占用了大约 5% 的运行时间,将 List 替换为提供更快 AddRange 的类可以将应用程序运行时间提高 4%。