繁体   English   中英

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

[英]Why is AddRange faster than using a foreach loop?

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

当我从4个结果stopwach1stopwach2stopwatch1比一直较低值stopwatch2 这意味着addrange总是比foreach快。 有谁知道为什么?

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

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

我怀疑你会发现,如果你再次尝试你的测试,但使用Enumerable.Range(0, 100000)作为fillData而不是List<T> ,两者将花费大约相同的时间。

如果您使用的是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 ^^^

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

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;
    }
}

正如你所看到的,有一些优化,比如确保容量()调用和使用 Array.Copy()。

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

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

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

这就像让服务员给你送一瓶啤酒十次,然后让他一次给你送十瓶啤酒。

你认为什么更快:)

我想这是优化内存分配的结果。 for AddRange 内存只分配一次,而 foreach 则在每次迭代时重新分配。

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

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

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

这是因为 Foreach 循环会将循环每次获得的所有值相加,并且
AddRange() 方法将收集它作为“块”获取的所有值,并立即将该块添加到指定位置。

简单理解,这就像你有一个清单,列出了要从市场上带来的 10 件物品,这样可以更快地将所有物品一件一件或全部带出来。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM