为什么AddRange比使用foreach循环更快?

时间:2012-03-23 09:08:39

标签: c# .net c#-4.0

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();

当我从stopwach1stopwach2获取 4 结果时,stopwatch1的值始终低于stopwatch2。这意味着addrange总是比foreach更快。 有谁知道为什么?

10 个答案:

答案 0 :(得分:83)

可能AddRange可以检查传递给它的值在哪里实现IListIList<T>。如果是,它可以找出该范围内有多少个值,因此需要分配多少空间......而foreach循环可能需要重新分配几次。

此外,即使在分配后,List<T>也可以使用IList<T>.CopyTo执行批量复制到基础数组中(当然,对于实现IList<T>的范围。)

我怀疑你会发现,如果你再次尝试测试,但使用Enumerable.Range(0, 100000)代替fillData代替List<T>,那么两者大约需要同一时间。

答案 1 :(得分:55)

如果您正在使用Add,则会根据需要(加倍)逐步调整内部数组的大小,从默认的起始大小10(IIRC)开始。如果您使用:

var manualFill = new List<int>(fillData.Count);

我希望它会彻底改变(不再调整大小/数据副本)。

从反射器,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 ^^^

答案 2 :(得分:17)

因为AddRange检查添加项的大小并且仅增加内部数组的大小一次。

答案 3 :(得分:6)

List AddRange方法的反射器反汇编具有以下代码

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()。

答案 4 :(得分:5)

使用AddRange时,Collection可以增加一次数组的大小,然后将值复制到其中。

使用foreach语句,集合需要多次增加集合的大小。

增加thr大小意味着复制需要时间的完整数组。

答案 5 :(得分:4)

这就像要求服务员给你一杯啤酒十次并要求他一次给你带来10瓶啤酒。

您认为什么更快:)

答案 6 :(得分:2)

我想这是优化内存分配的结果。 对于AddRange,内存只分配一次,并且每次迭代时都会重新分配。

也可能在AddRange实现中有一些优化(例如memcpy)

答案 7 :(得分:1)

在手动添加项目之前尝试初始化初始列表容量:

var manualFill = new List<int>(fillData.Count); 

答案 8 :(得分:1)

这是因为Foreach循环将添加循环每次获取的所有值和
AddRange()方法将收集它所获得的所有值作为&#34; chunk&#34;并将该块一次添加到指定位置。

简单地理解,就像你有一个从市场上带来的10个项目的清单,这将更快地将所有这些项目一次性或全部一次性地带来。

答案 9 :(得分:0)

AddRange扩展不会遍历每个项目,而是将每个项目作为一个整体应用。 foreach和.AddRange都有一个目的。 Addrange将赢得当前形势的速度竞赛。

更多相关信息:

Addrange Vs Foreach