为什么AddRange比使用foreach循环更快?
var fillData = new List<int>(); for (var i = 0; i < 100000; i++) { fillData.Add(i); } var stopwatch1 = new Stopwatch(); stopwatch1.Start(); var autoFill = new List<int>(); autoFill.AddRange(fillData); stopwatch1.Stop(); var stopwatch2 = new Stopwatch(); stopwatch2.Start(); var manualFill = new List<int>(); foreach (var i in fillData) { manualFill.Add(i); } stopwatch2.Stop();
当我从stopwach1
和stopwach2
4个结果时, stopwatch1
值始终低于stopwatch2
。 这意味着addrange
总是比foreach
快。 有谁知道为什么?
AddRange
可能会检查传递给它的值实现IList
或IList<T>
。 如果是这样,它可以找出有多less值在该范围内,因此需要分配多less空间…而foreach
循环可能需要重新分配多次。
另外,即使在分配之后, List<T>
也可以使用IList<T>.CopyTo
来执行批量复制到底层数组(对于实现IList<T>
的范围当然是)。
我怀疑你会发现,如果你再次尝试你的testing,但使用Enumerable.Range(0, 100000)
fillData
Enumerable.Range(0, 100000)
fillData
而不是一个List<T>
,两者将花费大致相同的时间。
如果使用Add
,它会根据需要逐渐调整内部数组的大小(加倍),默认大小为10(IIRC)。 如果你使用:
var manualFill = new List<int>(fillData.Count);
我预计它会发生根本性的变化(不再resize/数据复制)。
从reflection器, AddRange
内部,而不是增加倍增:
ICollection<T> is2 = collection as ICollection<T>; if (is2 != null) { int count = is2.Count; if (count > 0) { this.EnsureCapacity(this._size + count); // ^^^ this the key bit, and prevents slow growth when possible ^^^
因为AddRange
检查添加项目的大小,并增加一次内部数组的大小。
List AddRange方法的reflection器的反汇编具有以下代码
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; } }
正如你所看到的,有一些优化如EnsureCapacity()调用和使用Array.Copy()。
使用AddRange
,Collection可以增加一次数组的大小,然后将值复制到其中。
使用foreach
语句集合需要不止一次地增加集合的大小。
增加thr大小意味着复制需要时间的完整数组。
这就像是要求服务员给你带来一瓶啤酒十次,并要求他立即带上10杯啤酒。
你认为什么是更快:)
我想这是内存分配优化的结果。 AddRange内存只分配一次,而foreach每次迭代重新分配完成。
也可能在AddRange实现中有一些优化(例如memcpy)
这是因为Foreach循环会添加循环所获得的所有值
AddRange()方法将收集所有作为“块”获取的值,并将该块立即添加到指定的位置。
简单地理解,就像你有一个从市场上拿来的10件物品的清单,这将更快地把所有这一切,一个接一个或所有在一次。
AddRange扩展不会遍历每个项目,而是应用每个项目作为一个整体。 foreach和.AddRange都有一个目的。 Addrange将为您目前的情况赢得速度的比赛。
更多关于这里:
Addrange与Foreach
在手动添加项目之前尝试初始化初始列表容量:
var manualFill = new List<int>(fillData.Count);